2010-10-18 16 views
5

Devo creare un modello di strategia in cui un utente seleziona quattro strategie da un elenco di venti o trenta oggetti di strategia univoci. L'elenco delle strategie verrà ampliato con la maturazione del progetto e gli utenti potranno modificare la strategia selezionata in qualsiasi momento.Questo codice è troppo fragile?

Ho intenzione di memorizzare i nomi delle strategie che hanno selezionato come stringhe e quindi utilizzare un metodo come questo per caricare le classi di strategia che corrispondono alle stringhe selezionate.

class StrategyManager { // simplified for the example 
    public $selectedStrategies = array(); 
    public function __construct($userStrategies) { 
     $this->selectedStrategies = array(
      'first' => new $userStrategies['first'], 
      'second' => new $userStrategies['second'], 
      'third' => new $userStrategies['third'], 
      'fourth' => new $userStrategies['fourth'] 
     ); 
    } 

    public function do_first() { 
     $this->selectedStrategies['first']->execute(); 
    } 

    public function do_second() { 
     $this->selectedStrategies['second']->execute(); 
    } 

    public function do_third() { 
     $this->selectedStrategies['third']->execute(); 
    } 

    public function do_fourth() { 
     $this->selectedStrategies['fourth']->execute(); 
    } 
} 

Sto cercando di evitare una dichiarazione switch di grandi dimensioni. La mia preoccupazione è che questo sembra tipo Stringly Typed. Esiste un modo migliore per raggiungere questo obiettivo senza utilizzare un'istruzione switch condizionale o di grandi dimensioni?

BTW: L'utente non inserisce una stringa quando seleziona le quattro strategie. Avrei bisogno di mantenere un elenco di stringhe da presentare all'utente in una casella di selezione e aggiungerne di nuovi all'elenco mentre aggiungo nuovi oggetti strategia.

Spiegazione
ircmaxell espresso un po 'di confusione per quanto riguarda ciò che è che sto cercando di fare. Nell'esempio precedente, l'utente ha selezionato quattro strategie da un elenco e passate al costruttore StrategyManager come una serie di stringhe. Gli oggetti strategia corrispondenti vengono create e memorizzate in una matrice interna, $this->selectedStrategies

"primo", "secondo", "terzo" e "quarto" sono le chiavi dell'array della matrice interna per i quattro differenti strategie selezionate. Dopo che l'oggetto StrategyManager è stato creato, l'applicazione utilizza il metodo execute delle quattro strategie durante i vari momenti della durata del processo.

Quindi, in poche parole ... ogni volta che l'applicazione deve eseguire il metodo del numero di strategia "uno" lo fa, ed i risultati sono diversi a seconda di quale strategia è stata scelta dall'utente per Strategia "uno"

+0

Sono confuso. Sono "prima", "seconda", "terza" e "quarta" diverse strategie possibili, o sono una serie di comandi per la strategia selezionata (che vengono scelti prima di costruire il manager). E se così fosse, un modello di Chain of Responsibility (http://sourcemaking.com/design_patterns/chain_of_responsibility) o [Command] (http://sourcemaking.com/design_patterns/command) funzionerà meglio? Potresti spiegare cosa stai cercando di fare esattamente (e cosa fa il codice, perché esistono le diverse strategie)? – ircmaxell

+0

aggiornerò la domanda. – Stephen

+0

Ci sono sempre 4 strategie? E vengono sempre eseguiti in sequenza? O sono quattro strategie altrimenti non correlate che stai solo cercando di gestire insieme? – ircmaxell

risposta

1

Sulla base dei commenti e degli aggiornamenti, non penso che questo codice sia troppo fragile. Sarà più difficile mantenere se modifichi la catena di chiamate per un tipo di strategia (do_one, do_two, ecc.) O aggiungi strategie. Quello che vorrei invece raccomandare è usare un abstract factory per creare le "strategie". Quindi, nel codice in cui hai bisogno della strategia, recupera l'oggetto strategia stesso ...

Il motivo per cui questo metodo mi piace di più è due volte. In primo luogo, crea solo le strategie su richiesta, quindi non stai costruendo oggetti che non ti servono. In secondo luogo, incapsula la scelta dell'utente poiché è l'unico posto in cui è necessario cercarlo (è possibile costruirlo con l'integrazione delle dipendenze, ma è necessario un altro posto per gestire l'edificio).

class StrategyFactory { 

    protected $strategies = array(); 

    //If you like getter syntax 
    public function __call($method, $arguments) { 
     $method = strtolower($method); 
     if (substr($method, 0, 3) == 'get') { 
      $strategy = substr($method, 3); 
      return $this->getStrategy($strategy); 
     } 
     throw new BadMethodCallException('Unknown Method Called'); 
    } 

    public function getStrategy($strategy) { 
     if (isset($this->strategies[$strategy])) { 
      return $this->strategies[$strategy]; 
     } elseif ($this->makeStrategy($strategy)) { 
      return $this->strategies[$strategy]; 
     } 
     throw new LogicException('Could not create requested strategy'); 
    } 

    protected function makeStrategy($name) { 
     //pick strategy from user input 
     if ($strategyFound) { 
      $this->strategies[$name] = new $strategy(); 
      return true; 
     } else { 
      return false; 
     } 
    } 
} 

Quindi, utilizzare in questo modo:

$strategy = $factory->getSomeStrategyName(); 
$strategy->execute(); 

o anche con chaning:

$factory->getSomeStrategyName()->execute(); 

O senza metodi magici:

$factory->getStrategy('strategyName')->execute(); 
+0

Mi piace l'implementazione di fabbrica che hai fornito qui. Grazie! – Stephen

2

Hmm, beh, non penso che sia troppo fragile. Non hai bisogno delle stringhe però. Potresti semplicemente usare una matrice ordinata poiché la denominazione corrisponde comunque a 0,1,2,3. Se sei preoccupato che vengano fornite strategie o classi non valide, puoi inserire alcune conferme nel gestore.

public function __construct() { 
    $this->selectedStrategies = array(
     /* could add some default strategies */ 
    ); 
} 
public function load(array $userStrategies) { 
    for($i=0; $i<3; $i++) { 
     try { 
      $rc = new ReflectionClass($userStrategies[$i]); 
      if($rc->implementsInterface('Iterator')) { 
       $this->selectedStrategies[$i] = new $userStrategies[$i]; 
      } else { 
       throw new InvalidArgumentException('Not a Strategy'); 
      } 
     } catch(ReflectionException $e) { 
      throw new InvalidArgumentException('Not a Class'); 
     } 
    } 
} 

E invece di chiamare le strategie con le chiavi associative, è sufficiente

$this->selectedStrategies[0]->execute(); 

e così via.


Ancora un altro approccio sarebbe quello di utilizzare

class StrategyCollection 
{ 
    protected $strategies; 

    public function __construct() { 
     $this->strategies = new SplFixedArray(4); 
    } 

    public function add(IStrategy $strategy) { 
     $this->strategies[] = $strategy; 
     return $this; 
    } 
} 

e quindi riempire il Gestore/Collezione dall'esterno. Con il typehint per IStrategy puoi essere sicuro che solo le classi che implementano l'interfaccia di strategia finiscono nel gestore. Ciò ti risparmia le chiamate Reflection un po 'costose durante la creazione delle strategie. Lo SplFixedArray verifica che sia presente un'eccezione di runtime quando si tenta di aggiungere più di quattro strategie.


Su un sidenote, non fidarsi dell'input da una selectbox. Solo perché una selectbox fornisce opzioni fisse, non significa che gli utenti malintenzionati non possono armeggiare con la richiesta.Tutti i dati delle richieste devono essere disinfettati e ricontrollati.

+1

+1 per il sidenote e la classe StrategyCollection, che è semplice e ben costruita. – Iiridayn

+1

+1 per SplFixedArray. Bello. – Stephen

0

Se le funzioni di strategia don' t stato necessario, è possibile passare a un funzionale stile di programmazione e sostituire l'intera classe con: call_user_func($strategy['first']); (secondo, ecc.). Se preoccupati per lo spazio dei nomi globale, le loro funzioni potrebbero essere memorizzate come membri statici di una classe, ovvero call_user_func(array('Strategies', $strategy['first'])); Si potrebbe quindi ottenere un elenco di tutte le strategie valide (per generare e testare la selectbox) usando get_class_methods('Strategies');, che potrebbe semplificare il codice avendo appena l'unico elenco globale di strategie valide.

Se è necessario lo stato memorizzato con le funzioni di strategia - potrei utilizzare una sorta di funzione di chiamata di caching - qualcosa come

function doStrategy($class) { 
    static $selectedStrategies = array(); 

    if (!isset($selectedStrategies[$class])) { 
     $selectedStrategies[$class] = new $class; 
    } 

    $Strategy = $selectedStrategies[$class]; 
    $Strategy->execute(); // some versions of PHP require two lines here 
} 

Naturalmente si potrebbe ancora utilizzare una classe su una funzione per fare anche questo : P.

L'epiteto "Stringly Typed" non è applicabile per PHP poiché è sia debolmente digitato che già internamente utilizzando stringhe per memorizzare simboli (nomi di classi e funzioni, variabili, ecc.). In quanto tale, per riflettere il tipo di dati stringa è spesso la soluzione migliore. Non entreremo in ciò che questo significa per la lingua nel suo complesso.