Domanda

Ho guardato questa spiegazione su Wikipedia , in particolare l'esempio C ++, e non riesco a riconoscere la differenza tra definire 3 classi, creare istanze e chiamarle e quell'esempio. Quello che ho visto è stato semplicemente inserire altre due classi nel processo e non riesco a vedere dove ci sarebbe un vantaggio. Ora sono sicuro che mi manca qualcosa di ovvio (legno per gli alberi) - qualcuno potrebbe spiegarlo usando un esempio definitivo del mondo reale?


Quello che posso fare dalle risposte finora, mi sembra essere solo un modo più complesso di farlo:

have an abstract class: MoveAlong with a virtual method: DoIt()
have class Car inherit from MoveAlong, 
     implementing DoIt() { ..start-car-and-drive..}
have class HorseCart inherit from MoveAlong, 
     implementing DoIt() { ..hit-horse..}
have class Bicycle inherit from MoveAlong, 
     implementing DoIt() { ..pedal..}
now I can call any function taking MoveAlong as parm 
passing any of the three classes and call DoIt
Isn't this what Strategy intents? (just simpler?)

[Modifica-update] La funzione a cui mi riferisco sopra viene sostituita con un'altra classe in cui MoveAlong sarebbe un attributo impostato in base alle esigenze in base all'algoritmo implementato in questa nuova classe. (Simile a quanto dimostrato nella risposta accettata.)


[Modifica-aggiorna] Conclusione

Il modello di strategia ha i suoi usi, ma io sono un convinto sostenitore dei KISS, e tenderei a tecniche più semplici e meno offensive. Soprattutto dal momento che voglio trasmettere un codice facilmente gestibile (e quindi molto probabilmente sarò quello che dovrà apportare le modifiche!).

È stato utile?

Soluzione

Il punto è separare gli algoritmi in classi che possono essere collegate in fase di esecuzione. Ad esempio, supponiamo che tu abbia un'applicazione che include un orologio. Esistono molti modi diversi per disegnare un orologio, ma per la maggior parte la funzionalità sottostante è la stessa. Quindi puoi creare un'interfaccia di visualizzazione dell'orologio:

class IClockDisplay
{
    public:
       virtual void Display( int hour, int minute, int second ) = 0;
};

Quindi hai la tua classe Clock che è collegata a un timer e aggiorna il display dell'orologio una volta al secondo. Quindi avresti qualcosa del tipo:

class Clock
{
   protected:
      IClockDisplay* mDisplay;
      int mHour;
      int mMinute;
      int mSecond;

   public:
      Clock( IClockDisplay* display )
      {
          mDisplay = display;
      }

      void Start(); // initiate the timer

      void OnTimer()
      {
         mDisplay->Display( mHour, mMinute, mSecond );
      }

      void ChangeDisplay( IClockDisplay* display )
      {
          mDisplay = display;
      }
};

Quindi in fase di esecuzione si crea un'istanza dell'orologio con la classe di visualizzazione corretta. vale a dire che potresti avere ClockDisplayDigital, ClockDisplayAnalog, ClockDisplayMartian tutti implementando l'interfaccia IClockDisplay.

Quindi, in seguito, puoi aggiungere qualsiasi tipo di nuovo display dell'orologio creando una nuova classe senza dover fare confusione con la tua classe Clock e senza dover sostituire i metodi che possono essere disordinati per la manutenzione e il debug.

Altri suggerimenti

In Java si utilizza un flusso di input di crittografia per decrittografare in questo modo:

String path = ... ;
InputStream = new CipherInputStream(new FileInputStream(path), ???);

Ma il flusso di cifratura non è a conoscenza di quale algoritmo di crittografia si intende utilizzare o della dimensione del blocco, della strategia di riempimento, ecc ... Nuovi algoritmi verranno aggiunti continuamente, quindi non è pratico codificarli. Invece passiamo in un oggetto Cipher oggetto di strategia per dirgli come eseguire la decodifica ...

String path = ... ;
Cipher strategy = ... ;
InputStream = new CipherInputStream(new FileInputStream(path), strategy);

In generale usi il modello di strategia ogni volta che hai un oggetto che sa cosa deve fare ma non come per farlo. Un altro buon esempio sono i gestori di layout in Swing, sebbene in quel caso non abbia funzionato altrettanto bene, vedi Totally GridBag per un'illustrazione divertente.

NB: ci sono due modelli al lavoro qui, poiché il wrapping di stream in stream è un esempio di Decoratore .

C'è una differenza tra strategia e decisione / scelta. Il più delle volte a gestiremmo decisioni / scelte nel nostro codice e le realizzeremmo usando i costrutti if () / switch (). Il modello di strategia è utile quando è necessario disaccoppiare la logica / l'algoritmo dall'uso.

Ad esempio, Pensa a un meccanismo di polling, in cui diversi utenti controllerebbero risorse / aggiornamenti. Ora potremmo desiderare che alcuni utenti con privilegi vengano informati con tempi di risposta più rapidi o con maggiori dettagli. Essenzialmente la logica utilizzata cambia in base ai ruoli dell'utente. La strategia ha senso dal punto di vista del design / dell'architettura, ai livelli più bassi di granularità dovrebbe essere sempre messa in discussione.

Il modello di strategia ti consente di sfruttare il polimorfismo senza estendere la tua classe principale. In sostanza, stai inserendo tutte le parti variabili nell'interfaccia strategica e nelle implementazioni e i delegati della classe principale. Se il tuo oggetto principale usa solo una strategia, è quasi lo stesso di avere un metodo astratto (puro virtuale) e implementazioni diverse in ogni sottoclasse.

L'approccio strategico offre alcuni vantaggi:

  • puoi cambiare la strategia in fase di esecuzione - confrontala con la modifica del tipo di classe in fase di esecuzione, che è molto più difficile, specifica del compilatore e impossibile per i metodi non virtuali
  • una classe principale può usare più di una strategia che ti consente di ricombinarle in più modi. Considera una classe che cammina su un albero e valuta una funzione in base a ciascun nodo e al risultato corrente. Puoi avere una strategia di camminata (profondità prima o ampiezza) e strategia di calcolo (alcuni funzione - ovvero "conta numeri positivi" o "somma"). Se non usi strategie, dovrai implementare una sottoclasse per ogni combinazione di camminata / calcolo.
  • il codice è più facile da mantenere poiché la strategia di modifica o comprensione non richiede di comprendere l'intero oggetto principale

Lo svantaggio è che in molti casi il modello di strategia è eccessivo: l'operatore switch / case è lì per un motivo. Prendi in considerazione di iniziare con semplici istruzioni di flusso di controllo (switch / case o if), quindi solo se necessario passa alla gerarchia di classi e se hai più di una dimensione di variabilità, estrai le strategie da essa. I puntatori a funzione cadono da qualche parte nel mezzo di questo continuum.

Lettura consigliata:

Un modo per vedere questo è quando hai una varietà di azioni che vuoi eseguire e quelle azioni sono determinate in fase di esecuzione. Se si crea una tabella hash o un dizionario di strategie, è possibile recuperare quelle strategie che corrispondono a valori o parametri di comando. Una volta selezionato il tuo sottoinsieme, puoi semplicemente iterare l'elenco delle strategie ed eseguire in successione.

Un esempio concreto sarebbe il calcolo del totale di un ordine. I tuoi parametri o comandi sarebbero prezzo base, tassa locale, tassa comunale, tassa statale, spedizione via terra e sconto coupon. La flessibilità entra in gioco quando gestisci la variazione degli ordini: alcuni stati non avranno tasse sulle vendite, mentre altri ordini dovranno applicare un coupon. È possibile assegnare dinamicamente l'ordine dei calcoli. Finché hai preso in considerazione tutti i tuoi calcoli, puoi accogliere tutte le combinazioni senza ricompilare.

Questo modello di progettazione consente di incapsulare algoritmi nelle classi.

La classe che utilizza la strategia, la classe client, è disaccoppiata dall'implementazione dell'algoritmo. È possibile modificare l'implementazione degli algoritmi o aggiungere un nuovo algoritmo senza dover modificare il client. Questo può anche essere fatto in modo dinamico: il cliente può scegliere l'algoritmo che utilizzerà.

Ad esempio, immagina un'applicazione che deve salvare un'immagine in un file; l'immagine può essere salvata in diversi formati (PNG, JPG ...). Gli algoritmi di codifica saranno tutti implementati in classi diverse che condividono la stessa interfaccia. La classe client sceglierà una in base alle preferenze dell'utente.

Nell'esempio di Wikipedia, tali istanze possono essere passate in una funzione a cui non deve interessare a quale classe appartengono tali istanze. La funzione chiama esegui sull'oggetto passato e sa che accadrà la Cosa Giusta.

Un tipico esempio del modello di strategia è il modo in cui i file funzionano in Unix. Dato un descrittore di file, puoi leggerlo, scriverlo, scrutarlo, cercarlo, inviargli ioctl , ecc., Senza dover sapere se hai a che fare con un file , directory, pipe, socket, device, ecc. (Ovviamente alcune operazioni, come seek, non funzionano su pipe e socket. Ma in questi casi le letture e le scritture funzioneranno perfettamente.)

Ciò significa che puoi scrivere codice generico per gestire tutti questi diversi tipi di "file", senza dover scrivere codice separato per gestire file rispetto a directory, ecc. Il kernel Unix si occupa di delegare le chiamate al codice giusto .

Ora, questo è il modello di strategia usato nel codice del kernel, ma non hai specificato che doveva essere un codice utente, solo un esempio reale. : -)

Il modello di strategia funziona su un'idea semplice, ad esempio " Favor Composizione in ereditarietà " in modo che la strategia / l'algoritmo possano essere modificati in fase di esecuzione. Per illustrare facciamo un esempio in cui è necessario crittografare diversi messaggi in base al tipo, ad es. MailMessage, ChatMessage ecc.

class CEncryptor
{
    virtual void encrypt () = 0;
    virtual void decrypt () = 0;
};
class CMessage
{
private:
    shared_ptr<CEncryptor> m_pcEncryptor;
public:
    virtual void send() = 0;

    virtual void receive() = 0;

    void setEncryptor(cost shared_ptr<Encryptor>& arg_pcEncryptor)
    {
        m_pcEncryptor =  arg_pcEncryptor;
    }

    void performEncryption()
    {
        m_pcEncryptor->encrypt();
    }
};

Ora in fase di esecuzione è possibile creare un'istanza di diversi messaggi ereditati da CMessage (come CMailMessage: public CMessage) con crittografatori diversi (come CDESEncryptor: public CEncryptor)

CMessage *ptr = new CMailMessage();
ptr->setEncryptor(new CDESEncrypto());
ptr->performEncryption();
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top