A cosa serve e come funziona lo Strategy Pattern

Esistono molti design pattern nell’OOP, che servono a rispondere a diverse e specifiche esigenze, questi sono divisi in 3 macro categorie principali:

Creazionali, Strutturali, Comportamentali

In questo articolo vi parlerò del pattern Strategy, che è di natura comportamentale.

L’obiettivo che si pone questo modello, è di aiutarvi a separare delle strategie dal contesto per rispondere a uno scopo comune.

Proverò ad essere “agnostico” sulla sintassi del codice, in modo da rimanere in un contesto teorico (per quanto possibile), passiamo a qualcosa di più concreto.

“Strategy” applicato a un Form di contatto

Un situazione pratica per questo modello, potrebbe come nell’esempio che segue, aggiornare le proprietà dei campi di un Form.

Consideriamo la condizione in cui il Form in questione, serve per contattare un azienda per richiedere informazioni generiche o su un loro prodotto specifico.

In questa situazione, l’esigenza, è che non tutti i campi siano obbligatori o visibili, ma che dipendano dalla selezione specifica del motivo per cui avviene il contatto.

Es.: se stiamo contattando l’azienda per chiedere informazioni sui loro orari di apertura, non sarà necessario mostrare dei campi relativi ai prodotti.

Iniziamo

Sarà indispensabile utilizzare un interfaccia, che definisca i metodi:

interface StrategyFormField{
    public function getRequiredField(): array;
    public function getVisibleField(): array;
}

Come potete vedere le classi “SimpleRequest” e “ProductRequest” implementano l’interfaccia “StrategyFormField”.
Anche se ognuna, è focalizzata a definire quali sono i campi collegati a una determinata modalità di contatto.

La classe che determina la sequenza di azioni per arrivare al risultato è “FormContext”.

Questa accetta nel costruttore una strategia di default iniziale (che potrebbe essere anche in alcuni contesti usata come fallback).

class FormContext{
private array requiredField;
private array visibleField;
private StrategyInterface strategy;
    public function __construct(StrategyInterface strategy){
        this.strategy = strategy;
    }
    public function setStrategy(StrategyInterface strategy): void
    {
        this.strategy = strategy;
    }
    public function applyStrategy(): void
    {
        this.requiredField = this.strategy.getRequiredField();
        this.visibleField = this.strategy.getVisibleField();
         //LA TUA LOGICA
    }
}

Questa classe è capace di accettare delle strategie diverse nel corso della sua esistenza utilizzando il metodo setStrategy(StrategyInterface strategy), perché deve rispondere a delle variazioni di comportamento, in base alla scelta della necessità di contatto dell’utente.

Mentre il metodo applyStrategy() ci aiuterà ad attuare le operazioni necessarie per aggiornare le proprietà dei campi restituiti della nostra strategia.

Approfondimenti

Qui si potrebbe già concludere l’articolo, ma, come diceva un mio caro ex-collega, è ora di mettere la “cravatta al baghino“.

Potremmo quindi disaccoppiare la logica che si occupa di interagire con il Form, dalla classe “FormContext”, utilizzando una classe specifica.

Iniziamo da un interfaccia che soddisfa le nostre esigenze, accettando dei campi richiesti e dei campi visibili e un metodo che applica le dovute modifiche.

interface FormManagerInterface{
    public function setForm(myForm);
    public function setRequiredField(Array requiredField);
    public function setVisibleField(Array visibleField);
    public function applyRules();
}

Dovremmo poi creare una classe, che implementi l’interfaccia “FormManagerInterface”, con dentro le operazioni del caso.
A questo punto iniettiamola nel costruttore di “FormContext”, pressapoco come segue:

class FormContext{
private array requiredField;
private array visibleField;
private StrategyInterface strategy;
private FormManagerInterface formManager;
    public function __construct(
        StrategyInterface strategy, 
        FormManagerInterface formManager
    ){
        this.strategy = strategy;
        this.formManager = formManager;
    }
    public function setStrategy(StrategyInterface strategy): void
    {
        this.strategy = strategy;
    }
    public function applyStrategy(): void
    {
        this.requiredField = this.strategy.getRequiredField();
        this.visibleField = this.strategy.getVisibleField();
        this.formManager.setRequiredField(this.requiredField);
        this.formManager.setVisibleField(this.visibleField);
        this.formManager.applyRules();
    }
}

In questo modo, la capacità di modificare le proprietà dei campi del Form è disaccoppiata dal contesto che è separato dalle strategie.

Seguendo queste indicazioni teoriche, dovreste poter creare del codice flessibile e mantenibile, aperto a nuovo sviluppi.

Questo articolo è ispirato a un contesto reale, in cui un Form viene manipolato utilizzando Javascript, cambiando le strategie in base all’opzione scelta in un selettore.

Leave a Reply