Meglio Capire il modello di progettazione 'Strategia'

8

Sono stato interessato a modelli di design per un po 'e ho iniziato a leggere "Head First Design Patterns". Ho iniziato con il primo modello chiamato pattern "Strategia". Ho affrontato il problema descritto nelle immagini sottostanti e ho prima cercato di proporre una soluzione da solo in modo da comprendere davvero l'importanza del modello.

Quindi la mia domanda è: perché la mia soluzione al problema qui sotto non è abbastanza buona. Quali sono i lati positivi / negativi della mia soluzione rispetto al modello? Ciò che rende chiaramente il modello l'unica soluzione praticabile?

LA MIA SOLUZIONE

Classe genitore: DUCK

<?php
class Duck
{
 public  $swimmable;
 public  $quackable;
 public  $flyable;

 function display()
 {
  echo "A Duck Looks Like This<BR/>";
 }

 function  quack()
 {
  if($this->quackable==1)
  {
   echo("Quack<BR/>");
  }
 }

 function swim()
 {
  if($this->swimmable==1)
  {
   echo("Swim<BR/>");
  }
 }

 function  fly()
 {
  if($this->flyable==1)
  {
   echo("Fly<BR/>");
  }
 }


}
?>

CLASSE EREDITARIA: MallardDuck

<?php
class MallardDuck extends Duck
{
 function MallardDuck()
 {
  $this->quackable = 1;
  $this->swimmable = 1;
 }

 function display()
 {
  echo "A Mallard Duck Looks Like This<BR/>";
 }
}
?>

CLASSE EREDITARIA: WoddenDecoyDuck

<?php
class WoddenDecoyDuck extends Duck
{
 function woddendecoyduck()
 {
  $this->quackable = 0;
  $this->swimmable = 0;
 }

 function display()
 {
  echo "A Wooden Decoy Duck Looks Like This<BR/>";
 }
}
    
posta Imran Omar Bukhsh 30.01.2011 - 09:11
fonte

6 risposte

6

Il tuo codice si interromperà quando, ad es. anatre ciarlatano con suoni diversi. Il booleano porterà solo ad altri booleani in dichiarazioni if molto pelose.

Il modello di strategia è semplice però. Prendi in questo caso il metodo ciarlatano e inseriscilo nella sua classe o interfaccia. Un'interfaccia in php sarebbe una classe che non contiene alcuna implementazione diversa dai metodi che sono stub (cioè non succede niente quando li chiami).

class Quackable {
    function quack() {}
}

In questo modo puoi creare diverse implementazioni quackable:

class DuckQuack extends Quackable {
    function quack() {
        return "Quack!";
    }
}

class SilentQuack extends Quackable {
    function quack() {
        return ""; 
            // The decoy duck can't quack. It is silent.
    }
}

E poiché abbiamo fatto il modello strategico, possiamo aggiungere altri tipi di "ciarlataneria":

class DoubleQuack extends Quackable {
    function quack() {
        return "Quackety-quack!"
    }
}

Lo stesso può essere applicato ai metodi di volo e nuoto per la classe Duck. Il modo in cui implementate una classe Duck con un quackable sarebbe come questo (l'interfaccia quackable è fornita attraverso il costruttore, che è una specie di come funziona l'injection dependency):

class Duck {
    protected $quackable;

    // The constructor
    function __construct($quackable) {
        $this->quackable = quackable;
    }

    function quack() {
        echo $this->quackable->quack();
    }
}

Implementare l'anatra selvatica e l'anatra esca sarà semplice dato che devi solo fornire il tipo di ciarlatani che l'anatra dovrebbe fare:

class MallardDuck extends Duck {
    function __construct() {
        parent::__construct(new DuckQuack());
           // we construct the mallard duck with a duck quack
    }
}

class DecoyDuck extends Duck {
    function __construct() {
        parent::__construct(new SilentQuack());
           // we construct the decoy duck with a silent quack
    }
}

L'utilizzo di tutto è semplice:

$duck1 = new MallardDuck();
$duck2 = new DecoyDuck();

$duck1.quack(); // echoes out "Quack!"
$duck2.quack(); // echoes out "" (because decoy ducks don't quack)

Spero che tutto questo abbia un senso per te.

    
risposta data 30.01.2011 - 13:39
fonte
4

Prima di tutto, il tuo problema non ha nulla a che fare con il modello di strategia. L'idea del modello strategico è di considerare la responsabilità di un determinato comportamento in una classe diversa. In questo modo, puoi collegare diversi comportamenti in un'unica istanza in fase di esecuzione.

Ora la tua soluzione funziona abbastanza bene per lo scenario in cui ti trovi, ma lo scenario è solo un esercizio, che è abbastanza lontano dai problemi del mondo reale.
Se si utilizza questa tecnica per affrontare grandi progetti, si rischia di finire con 5-10 livelli di ereditarietà, in cui ciascuna sottoclasse è accoppiata a un numero sempre maggiore di flag. Tale codice è estremamente fragile e verbous. Giocare con lo stato interno di tutte le super classi non è esattamente il modo OOP di gestire queste cose, perché offusca la separazione delle preoccupazioni.

Una soluzione che utilizza le interfacce è significativamente più pulita, più robusta e quindi si dimostrerà più manutenibile nel tempo. Cerca di non creare un'anatra base per tutti gli usi, ma piuttosto di fare astrazioni chiare sui diversi aspetti delle diverse anatre. Recentemente ho creato un post di blog su questo argomento, che potresti trovare utile.

    
risposta data 30.01.2011 - 14:34
fonte
3

Ah, bella domanda. Non penso che sia una buona pratica avere variabili membro che attivano la funzionalità. Principalmente il problema con questa soluzione è che, poiché esporti i metodi fly e quack sulla classe Duck , sembrerebbe che tu stia dicendo che "Tutte le anatre possono fly / quack ". Qual è il peggio è che, a seconda del tipo di runtime dell'istanza anatra che ho, fly o quack può o non può fare nulla. Questo può portare a un codice molto confuso.

Dove, come nell'esempio che il libro dà, quando ti aspetti un'anatra che può volare, puoi digitare (o testare, dato che stai usando un linguaggio dinamico) se fosse un Flyable duck.

Se ogni volta che ho un'anatra, prima chiamo quack su di essa, devo decidere di controllare se la variabile membro flyable è impostata su true che porterebbe a un sacco di duplicazione del codice.

if($duck->quackable) $duck->quack();

L'interfaccia Flyable aiuta a stabilire aspettative. Sì, il tuo metodo predefinito quack controllerà se dovrebbe o meno, ma io, in qualità di chiamante, non ho garanzie che sia addirittura sicuro chiamare quack . Pensa anche allo scenario con RubberDucky che dovrebbe cigolare piuttosto che ciarlare. Quando esegui l'override del metodo quack devi sapere per controllare la variabile membro $quackable prima di eseguire qualsiasi azione.

Un'altra cosa che penso rende poco chiara questa soluzione è che, dal momento che stai utilizzando un linguaggio dinamico come PHP, rende difficile impostare le aspettative sui tipi di input. (Non che ci sia qualcosa di sbagliato in questo, ma un linguaggio tipizzato staticamente potrebbe renderlo più facile da capire.)

Spero che questo aiuti e abbia senso.

    
risposta data 30.01.2011 - 09:49
fonte
1

Il tuo codice diventerà rapidamente disordinato nel caso in cui tu abbia dichiarato 10 diversi tipi di anatre che hanno tutte combinazioni diverse di dire 3 diverse varianti di volare, ciarlatano e nuotare.

Personalmente, ho scoperto che il vero valore del modello strategico è diventato chiaro quando ho iniziato a scrivere test unitari e utilizzando l'iniezione di dipendenza. Se non lo stai utilizzando, avrai molti problemi a gestire le dipendenze e quindi a creare oggetti nei test.

Si arriva a situazioni in cui creare un'anatra diventa complicata, quindi si potrebbe voler scrivere un test unitario per alcuni dei metodi di quacking o di volo, ma non è possibile farlo senza creare un'anatra nel test.

    
risposta data 30.01.2011 - 11:50
fonte
1

Avere uno stato booleano per memorizzare se le anatre possono abbaiare o volare, è una soluzione molto specifica al problema specifico che si sta affrontando nel progetto di classe e potrebbe non essere applicabile ad altri casi in cui il modello strategico è appropriato.

Penso che il tuo approccio "quackable" renda il codice un po 'più complicato. È una cosa in più che tu e gli utenti della classe dovete essere consapevoli di quanto sarebbe necessario se steste usando un modello di strategia.

Infine, poiché il codice di esempio non mostra alcun esempio di come ignorare i metodi "ciarlatano" e "volare", non si nota un enorme vantaggio dell'uso del modello strategico, riducendo la duplicazione del codice. Cosa faresti con una dozzina di diverse classi di anatra che tra loro hanno 3 diversi comportamenti "volanti"? Usando il tuo approccio, probabilmente ti ritroverai a tagliare e incollare, e quindi ad estrarre il codice duplicato in classi statiche di "helper". Il modello di strategia è molto più pulito.

    
risposta data 30.01.2011 - 12:15
fonte
1

Una bella demo visiva vale sempre più di mille parole.

John Lindquist sta facendo un ottimo lavoro per registrare screencast su diversi modelli di design. Puoi trovare i suoi 50 centesimi su questo particolare modello (e molto altro) sul suo blog

    
risposta data 29.04.2011 - 17:34
fonte

Leggi altre domande sui tag