Skip to content

Commit 54d2096

Browse files
Added to catalog of terms: Strategy Pattern (#196)
Authored-by: Ledjon Behluli <[email protected]>
1 parent c5e3b90 commit 54d2096

File tree

3 files changed

+294
-1
lines changed

3 files changed

+294
-1
lines changed

docs/catalog-of-terms/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@
7171
- Read Model
7272
- Repositories (DDD)
7373
- Single Responsibility Principle
74-
- Strategy Pattern
74+
- [Strategy Pattern](Strategy-Pattern/)
7575
- Stub
7676
- Synchronous Communication
7777
- Transaction (Database)
Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
# Strategy Pattern
2+
3+
## Definition
4+
5+
*The strategy pattern (also known as the policy pattern) is a behavioral software design pattern that enables selecting an algorithm at runtime. Instead of implementing a single algorithm directly, code receives run-time instructions as to which in a family of algorithms to use.*
6+
7+
Source: [Wikipedia](https://en.wikipedia.org/wiki/Strategy_pattern)
8+
9+
## Example
10+
11+
### Model
12+
13+
![](http://www.plantuml.com/plantuml/png/jLDDQnin4BtFhnZIYzFQsxin9lM6f8KU38PUYooDNL5z66cMPd7wtwlrHifs3Oqfs63GpBpvUBFpxYABm8qrS13ofzWJtZoIew3b3RwxF_tm2DA86B4scXmd4sSelMDwOlWDETWx-cZa89ZsBU07ZCIR5tEI_RTTGFd9RPUlKsBO2KcOSNZiulH4ic7gGQM93CIKWPykHgxEaGbREA-QTjDiempwmDgxS-uZGEsj5KvzJdz3eQp4aUoYdRKEMj9N7Vb1IFQXhUf0Wgcu9w_mmTWbt9VyVaYsTllDO9zzdObcid6A1J1Ox2FngKvgqJWERUqLJJ4EnbzJq5vDKNP9QRZHT_Yo_hig7l-tR25shmD9_YPCGm_1syBpgfskKJoUqaXTbKhgzqChGh87Rj6ItLA802y2d2d_oysUbrbpaBNtVZOh6e8on-8vkS-KyqPy1U0y4nhQCVfTRZMVAu-0jJ0cucAxp8AkYh0M7xTB8AUmImU0Vmkdfx9ylNl8hvxD-19Xw2ZJltLTbsTVc73v5OpMM63pURwCuJh7Ug_A-LHLDLhjNNerrlm1)
14+
15+
### Code
16+
17+
```csharp
18+
19+
internal class BuySubscriptionCommandHandler : ICommandHandler<BuySubscriptionCommand, Guid>
20+
{
21+
private readonly IAggregateStore _aggregateStore;
22+
23+
private readonly IPayerContext _payerContext;
24+
25+
private readonly ISqlConnectionFactory _sqlConnectionFactory;
26+
27+
internal BuySubscriptionCommandHandler(
28+
IAggregateStore aggregateStore,
29+
IPayerContext payerContext,
30+
ISqlConnectionFactory sqlConnectionFactory)
31+
{
32+
_aggregateStore = aggregateStore;
33+
_payerContext = payerContext;
34+
_sqlConnectionFactory = sqlConnectionFactory;
35+
}
36+
37+
public async Task<Guid> Handle(BuySubscriptionCommand command, CancellationToken cancellationToken)
38+
{
39+
var priceList = await PriceListFactory.CreatePriceList(_sqlConnectionFactory.GetOpenConnection());
40+
41+
var subscription = SubscriptionPayment.Buy(
42+
_payerContext.PayerId,
43+
SubscriptionPeriod.Of(command.SubscriptionTypeCode),
44+
command.CountryCode,
45+
MoneyValue.Of(command.Value, command.Currency),
46+
priceList);
47+
48+
_aggregateStore.AppendChanges(subscription);
49+
50+
return subscription.Id;
51+
}
52+
}
53+
54+
public static class PriceListFactory
55+
{
56+
public static async Task<PriceList> CreatePriceList(IDbConnection connection)
57+
{
58+
var priceListItemList = await GetPriceListItems(connection);
59+
60+
var priceListItems = priceListItemList
61+
.Select(x =>
62+
new PriceListItemData(
63+
x.CountryCode,
64+
SubscriptionPeriod.Of(x.SubscriptionPeriodCode),
65+
MoneyValue.Of(x.MoneyValue, x.MoneyCurrency),
66+
PriceListItemCategory.Of(x.CategoryCode)))
67+
.ToList();
68+
69+
// This is place for selecting pricing strategy based on provided data and the system state.
70+
IPricingStrategy pricingStrategy = new DirectValueFromPriceListPricingStrategy(priceListItems);
71+
72+
return PriceList.Create(
73+
priceListItems,
74+
pricingStrategy);
75+
}
76+
77+
public static async Task<List<PriceListItemDto>> GetPriceListItems(IDbConnection connection)
78+
{
79+
var priceListItems = await connection.QueryAsync<PriceListItemDto>("SELECT " +
80+
$"[PriceListItem].[CountryCode] AS [{nameof(PriceListItemDto.CountryCode)}], " +
81+
$"[PriceListItem].[SubscriptionPeriodCode] AS [{nameof(PriceListItemDto.SubscriptionPeriodCode)}], " +
82+
$"[PriceListItem].[MoneyValue] AS [{nameof(PriceListItemDto.MoneyValue)}], " +
83+
$"[PriceListItem].[MoneyCurrency] AS [{nameof(PriceListItemDto.MoneyCurrency)}], " +
84+
$"[PriceListItem].[CategoryCode] AS [{nameof(PriceListItemDto.CategoryCode)}] " +
85+
"FROM [payments].[PriceListItems] AS [PriceListItem] " +
86+
"WHERE [PriceListItem].[IsActive] = 1");
87+
88+
var priceListItemList = priceListItems.AsList();
89+
return priceListItemList;
90+
}
91+
}
92+
93+
public class PriceList : ValueObject
94+
{
95+
private readonly List<PriceListItemData> _items;
96+
97+
private readonly IPricingStrategy _pricingStrategy;
98+
99+
private PriceList(
100+
List<PriceListItemData> items,
101+
IPricingStrategy pricingStrategy)
102+
{
103+
_items = items;
104+
_pricingStrategy = pricingStrategy;
105+
}
106+
107+
public static PriceList Create(
108+
List<PriceListItemData> items,
109+
IPricingStrategy pricingStrategy)
110+
{
111+
return new PriceList(items, pricingStrategy);
112+
}
113+
114+
public MoneyValue GetPrice(
115+
string countryCode,
116+
SubscriptionPeriod subscriptionPeriod,
117+
PriceListItemCategory category)
118+
{
119+
CheckRule(new PriceForSubscriptionMustBeDefinedRule(countryCode, subscriptionPeriod, _items, category));
120+
121+
return _pricingStrategy.GetPrice(countryCode, subscriptionPeriod, category);
122+
}
123+
}
124+
125+
public interface IPricingStrategy
126+
{
127+
MoneyValue GetPrice(
128+
string countryCode,
129+
SubscriptionPeriod subscriptionPeriod,
130+
PriceListItemCategory category);
131+
}
132+
133+
public class DiscountedValueFromPriceListPricingStrategy : IPricingStrategy
134+
{
135+
private readonly List<PriceListItemData> _items;
136+
137+
private readonly MoneyValue _discountValue;
138+
139+
public DiscountedValueFromPriceListPricingStrategy(
140+
List<PriceListItemData> items,
141+
MoneyValue discountValue)
142+
{
143+
_items = items;
144+
_discountValue = discountValue;
145+
}
146+
147+
public MoneyValue GetPrice(string countryCode, SubscriptionPeriod subscriptionPeriod, PriceListItemCategory category)
148+
{
149+
var priceListItem = _items.Single(x =>
150+
x.CountryCode == countryCode && x.SubscriptionPeriod == subscriptionPeriod &&
151+
x.Category == category);
152+
153+
return priceListItem.Value - _discountValue;
154+
}
155+
}
156+
157+
public class DirectValuePricingStrategy : IPricingStrategy
158+
{
159+
private readonly MoneyValue _directValue;
160+
161+
public DirectValuePricingStrategy(MoneyValue directValue)
162+
{
163+
_directValue = directValue;
164+
}
165+
166+
public MoneyValue GetPrice(string countryCode, SubscriptionPeriod subscriptionPeriod, PriceListItemCategory category)
167+
{
168+
return _directValue;
169+
}
170+
}
171+
172+
public class DirectValueFromPriceListPricingStrategy : IPricingStrategy
173+
{
174+
private readonly List<PriceListItemData> _items;
175+
176+
public DirectValueFromPriceListPricingStrategy(List<PriceListItemData> items)
177+
{
178+
_items = items;
179+
}
180+
181+
public MoneyValue GetPrice(
182+
string countryCode,
183+
SubscriptionPeriod subscriptionPeriod,
184+
PriceListItemCategory category)
185+
{
186+
var priceListItem = _items.Single(x =>
187+
x.CountryCode == countryCode && x.SubscriptionPeriod == subscriptionPeriod &&
188+
x.Category == category);
189+
190+
return priceListItem.Value;
191+
}
192+
}
193+
```
194+
### Description
195+
196+
Let's introduce the concepts of the strategy pattern, so we can understand how the above example fits this pattern.
197+
198+
* **Client** - The calling code.
199+
* **Context** - An object which maintains a reference to one of the *concrete strategies* and communicates with the *client*.
200+
* **Strategy interface** - An interface or abstract class that the *client* can use to set a concrete strategy at run-time, through the *context*.
201+
* **Concrete strategies** - One or more implementations of the *strategy interface*.
202+
203+
---
204+
205+
If we have a close look at our example of buying a `Subscription`, we can notice the elements of the strategy pattern.
206+
207+
* `BuySubscriptionCommandHandler` is the calling code! Also the handler, indirectly via the `PriceListFactory` sets the current *strategy* of `PriceList`, so their combined interaction represents the **Client**.
208+
* `PriceList` is the object which maintains a reference to a pricing strategy, so it represents the **Context**.
209+
* `IPricingStrategy` represents the **Strategy interface**.
210+
* `DiscountedValueFromPriceListPricingStrategy`, `DirectValueFromPriceListPricingStrategy` and `DirectValuePricingStrategy` are the implementations of `IPricingStrategy` so they represent the **Concrete strategies**.
211+
212+
---
213+
214+
The interaction of the `BuySubscriptionCommandHandler` and `PriceListFactory` is a good example of leveraging multiple design patterns. Check out [Factory Pattern](../Factory-Pattern/) to learn more.
215+
216+
Strategy should not be confused with [Decorator](../Decorator-Pattern/)!!!
217+
*A strategy lets you change the guts of an object, while decorator lets you change the skin.*
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
@startuml
2+
package "Generic" #DDDDDD {
3+
class Client {
4+
- context
5+
}
6+
7+
class Context {
8+
- strategy
9+
+ setStrategy(strategy)
10+
+ do()
11+
}
12+
13+
interface Strategy {
14+
+ execute()
15+
}
16+
17+
class ConcreteStrategyA {
18+
+ execute()
19+
}
20+
21+
class ConcreteStrategyB {
22+
+ execute()
23+
}
24+
}
25+
26+
package "BuySubscription" #DDDDDD {
27+
class BuySubscriptionCommandHandler {
28+
- connection
29+
- PriceListFactory.CreatePriceList(connection)
30+
}
31+
32+
class PriceList {
33+
- _pricingStrategy
34+
+ Create(items, pricingStrategy)
35+
+ GetPrice(countryCode, subscriptionPeriod, category)
36+
}
37+
38+
interface IPricingStrategy {
39+
+ GetPrice(countryCode, subscriptionPeriod, category)
40+
}
41+
42+
class DirectValueFromPriceListPricingStrategy {
43+
+ GetPrice(countryCode, subscriptionPeriod, category)
44+
}
45+
46+
class DirectValuePricingStrategy {
47+
+ GetPrice(countryCode, subscriptionPeriod, category)
48+
}
49+
50+
class DiscountedValueFromPriceListPricingStrategy {
51+
+ GetPrice(countryCode, subscriptionPeriod, category)
52+
}
53+
}
54+
55+
hide empty members
56+
57+
Client -down-|> Context
58+
Context *-- Strategy
59+
Strategy <|-- ConcreteStrategyA
60+
Strategy <|-- ConcreteStrategyB
61+
62+
note left of Context::do
63+
Calls <b>strategy.execute()</b>
64+
end note
65+
66+
67+
BuySubscriptionCommandHandler -down-> PriceList
68+
PriceList *-- IPricingStrategy
69+
IPricingStrategy <|-- DirectValueFromPriceListPricingStrategy
70+
IPricingStrategy <|-- DirectValuePricingStrategy
71+
IPricingStrategy <|-- DiscountedValueFromPriceListPricingStrategy
72+
73+
note left of PriceList::GetPrice
74+
Calls <b>_pricingStrategy.GetPrice(...)</b>
75+
end note
76+
@enduml

0 commit comments

Comments
 (0)