Composite

Per sollevare un altro problema interessante dei requisiti e della progettazione: come gestire il caso di politiche di determinazione dello sconto multiple che entrano in conflitto?
Immaginiamo che in un negozio siano attivi i seguenti sconti:

  • politica di sconto del 20% per gli anziani
  • sconto del 15% ai clienti abituali per vendite superiori a 400 dollari
  • il lunedì viene applicato uno sconto di 50 dollari sugli acquisti superiori a 500 dollari
  • chi compra una confezione di tè Darjeeling, riceve uno sconto del 15% su ogni cosa.

Si supponga che un anziano che sia anche un cliente abituale acquisti una confezione di tè Darjeeling e spenda 600 dollari in hamburger vegetariani. Quale politica di sconto deve essere applicata?

Una parte della risoluzione di questo problema richiede una strategia di risoluzione del conflitto del negozio: un negozio potrebbe applicare lo sconto migliore per il cliente o lo sconto migliore per il negozio stesso.

Il primo punto da notare è che possono coesistere più strategie; ovvero, una sola vendita può avere diverse strategie di determinazione del prezzo. Un altro punto da notare è che una strategia di determinazione del presso può essere relativa al tipo di cliente (per esempio un anziano). Questo ha delle implicazioni nella progettazione della creazione: il tipo di cliente deve essere noto alla PricingStrategyFactory al momento della creazione di una strategia di determinazione del presso per il cliente.

In modo simile, una strategia di determinazione del prezzo può essere correlata al tipo di prodotto acquistato. Anche questo ha delle implicazioni nella progettazione della creazione: la ProductDescription deve essere nota alla PricingStrategyFactory al momento della creazione di una strategia di determinazione del prezzo influenzata dal prodotto.

C'è un modo per modificare il progetto in modo tale che l'oggetto Sale non sappia se si trova di fronte a una sola o a più strategie di determinazione del prezzo e offrire anche un progetto per la risoluzione dei conflitti? Il pattern Composite offre una soluzione.

Tabella riassuntiva del pattern
...

NomeComposite
ProblemaCome trattare un gruppo o una struttura composta di oggetti (polimorficamente) dello stesso tipo nello stesso modo di un oggetto non composto (atomico)?
SoluzioneDefinire le classi per gli oggetti composti e atomici in modo che implementino la stessa interfaccia.

Per esempio, una nuova classe chiamata CompositeBestForCustomerPricingStrategy può implementare l'interfaccia ISalePricingStrategy.

Pasted image 20230526184734.png

Cosa succede?
...

La classe aggiunta CompositePricingStrategy implementa ISalePricingStrategy, questa nuova classe si estende in:

  • CompositeBestForCustomePricingStrategy
  • CompositeBestForStorePricingStrategy
    Inoltre CompositePricingStrategy possiede come attributo una lista di oggetti che implementano ISalePricingStrategy chiamata pricingStrategies, tale attributo viene ereditato dalle classi in cui CompositePricingStrategy si estende: CompositeBestForCustomerPricingStrategy e CompositeBestForStorePricingStrategy. Si tratta di una caratteristica distintiva di Composite.
Caratteristica di composite

L'oggetto composto esterno (CompositePricingStrategy e i suoi figli) contiene una lista di oggetti interni che implementano la stessa interfaccia.

Per questo motivo è possibile associare all'oggetto Sale un oggetto composto CompositeBestForCustomePricingStrategy (che contiene altre strategie al suo interno) oppure un oggetto atomico PercentDiscountPricingStrategy, e l'oggetto Sale non si rende conto se la strategie di determinazione del prezzo è atomica o no. Semplicemente è un oggetto che implementa l'interfaccia ISalePricingStrategy e capisce il messaggio getTotal.

Vediamo in codice Java come sarebbe
...

// superclasse tale che tutte le sue sottoclassi eriditano una lista di strategie
public abstract class CompositePricingStrategy implements ISalePricingStrategy{
	protected ArratList<ISalePricingStrategy> pricingStrategies = new ArrayList<>();

	public void add(ISalePricingStrategy s){
		pricingStrategy.add(s); // aggiunge strategia alla lista
	}

	public absract Money getTotal(Sale sale);
}
public class CompositeBestForCustomerPricingStrategy extends CompositePricingStrategy{
	public Money getTotal(Sale sale){
		Money lowestTotal = new Money(Integer.MAX_VALUE);

		// itera su tutte le strategie interne calcolando lo sconto per ogni strategia
		for(ISalePricingStrategy strategy : pricingStrategies){
			Money total = strategy.getTotal(sale);
			lowestTotal = total.min(lowestTotal);
		}
		return lowestTotal;
	}
}

Creare più oggetti SalePricingStrategy
...

Con il pattern Composite, un gruppo di più strategie di determinazione del prezzo (conflittuali) sono viste dall'oggetto Sale come una strategia di determinazione del prezzo singola. L'oggetto composto che contiene il gruppo implementa anch'esso l'interfaccia ISalePricingStrategy. Una parte più difficile di questo problema di progettazione è quando creare queste strategie.

Un progetto ottimale inizierà con la creazione di un Composite che contiene inizialmente la politica di sconto attuale del negozio (che potrebbe essere imposta a uno sconto dello 0% se non è attiva alcuna politica di sconto), come per esempio una PercentageDiscountPricingStrategy. Successivamente, se in un passo successivo del caso d'uso viene scopera un'altra strategia di determinazione del prezzo da applicare (come uno sconto per anziani), sarà facile aggiungerla all'oggetto composto, utilizzando il metodo add ereditato da CompositePricingStrategy.

Pasted image 20230526191234.png