Patterns
2 - Strategy Pattern
Lo Strategy Pattern ha come obiettivo quello di spostare un algoritmo dalla classe in cui viene utilizzato in una separata: poiché ci possono essere
problemi risolvibili con algoritmi (Strategies) differenti, si evita di averli tutti insieme in una singola classe (accumulo di codice e istruzioni condizionali),
utilizzando invece la classe corretta per l'algoritmo che si vuole adottare.
L'esempio classico è quello dell'applicazione di un algoritmo di ordinamento (quicksort, mergesort etc.), utilizzando quello più corretto in base agli
elementi che si vogliono ordinare (quantità di elementi, dispersione etc.).
Lo Strategy Pattern permette quindi di selezionare algoritmi diversi durante l'esecuzione.
Nell'esempio qui illustrato, abbiamo una classe che deve applicare una serie di sconti in percentuale ed in valore ad un prezzo lordo iniziale.
Possiamo pensare ad algoritmi diversi:
- applicare prima tutti gli sconti in percentuale poi quelli in valore
- al contrario, applicare prima tutti gli sconti in valore poi quelli in percentuale
- applicare prima il primo in percentuale e poi il primo a valore poi i secondi e così via
Di seguito il diagramma UML delle classi che realizzano lo Strategy Pattern:

Analisi del codice
Nello Strategy Pattern gli algoritmi sono definiti con specifiche interfacce e le interfacce sono implementate dalle classi concrete strategy. Tutto cià permette di disaccoppiare gli algoritmi e le classi che li usano. L'algoritmo usato puà essere modificato senza intervenire sulla classe che lo usa; inoltre gli algoritmi possono essere modificati sia run-time che design-time. Nel nostro esempio vediamo l'interfaccia che richiede un'algoritmo per il calcolo del valore scontato:
interface IDiscountCalculationStrategy
{
decimal Calculate(decimal fullPrice, List<Discount> list);
}
Quella che segue è un esempio di classe concrete strategy che implementa l'interfaccia; è evidente che è possibile definire più concrete class per la stessa interfaccia:
class DiscountCalcOne : IDiscountCalculationStrategy
{
public override decimal Calculate(decimal fullPrice, List<Discount> list)
{
//list.DiscountCalcOne(); not-implemented
return 1;
}
}
L'interfaccia viene utilizzata dalla classe che la contiene (context strategy); questa classe, in funzione del tipo di strategy richiesto dall'oggetto che la istanzia, utilizzerà la concrete strategy corretta:
class DiscountCalculation
{
private decimal _fullPrice;
private readonly List<Discount> _discountList = new List<Discount>();
private DiscountCalculationStrategy _discountCalculationStrategy;
public DiscountCalculation(decimal fullPrice, List<Discount> discountList)
{
_discountList = discountList;
_fullPrice = fullPrice;
}
public void SetDiscountCalculationStrategy(
DiscountCalculationStrategy discountCalculationStrategy)
{
_discountCalculationStrategy = discountCalculationStrategy;
}
public void AddDiscount(Discount _discount)
{
_discountList.Add(_discount);
}
public decimal Calculate()
{
return _discountCalculationStrategy.Calculate(_fullPrice, _discountList);
}
}