Question

J'ai consulté cette explication sur Wikipedia , en particulier l'exemple C ++, sans succès. reconnaissez la différence entre simplement définir 3 classes, créer des instances et les appeler, et cet exemple. Ce que j'ai vu ne faisait que placer deux autres classes dans le processus et je ne voyais pas où il y aurait un avantage. Maintenant, je suis sûr qu’il me manque quelque chose d’évident (le bois pour les arbres) - quelqu’un pourrait-il expliquer en utilisant un exemple concret du monde réel?

Ce que je peux tirer des réponses à ce jour, cela ne me semble pas être une manière plus complexe de le faire:

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?)

[Edit-update] La fonction mentionnée ci-dessus est remplacée par une autre classe dans laquelle MoveAlong serait un attribut défini en fonction des besoins en fonction de l'algorithme implémenté dans cette nouvelle classe. (Semblable à ce qui est démontré dans la réponse acceptée.)

[Edit-update] Conclusion

Le modèle de stratégie a ses utilisations, mais je suis un fervent partisan de KISS et aurais tendance à recourir à des techniques plus simples et moins opaques. Surtout parce que je veux transmettre du code facilement maintenable (et 'parce que ce sera probablement moi qui devra faire les changements!).

Était-ce utile?

La solution

Le but est de séparer les algorithmes en classes pouvant être connectées au moment de l'exécution. Par exemple, disons que vous avez une application qui inclut une horloge. Vous pouvez dessiner une horloge de nombreuses manières différentes, mais la fonctionnalité sous-jacente est généralement la même. Vous pouvez donc créer une interface d’affichage de l’horloge:

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

Ensuite, vous avez votre classe Clock connectée à une minuterie et qui met à jour l'affichage de l'horloge une fois par seconde. Donc, vous auriez quelque chose comme:

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;
      }
};

Ensuite, au moment de l'exécution, vous instanciez votre horloge avec la classe d'affichage appropriée. c'est-à-dire que vous pourriez avoir ClockDisplayDigital, ClockDisplayAnalog, ClockDisplayMartian mettant en œuvre l'interface IClockDisplay.

Ainsi, vous pourrez ajouter ultérieurement tout type de nouvel affichage d'horloge en créant une nouvelle classe sans devoir manipuler votre classe Clock, ni avoir à redéfinir les méthodes pouvant être compliquées à gérer et à déboguer.

Autres conseils

En Java, vous utilisez un flux d'entrée chiffré pour déchiffrer de la manière suivante:

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

Mais le flux de chiffrement n’a aucune connaissance de l’algorithme de chiffrement que vous comptez utiliser, ni de la taille des blocs, de la stratégie de remplissage, etc. Les nouveaux algorithmes seront ajoutés en permanence, aussi leur codage en dur n’est pas pratique. Au lieu de cela, nous passons à un objet de stratégie Cipher pour lui indiquer comment effectuer le déchiffrement ...

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

En général, vous utilisez le modèle de stratégie chaque fois que vous avez un objet sachant ce qu'il doit faire, mais pas comment le faire. Les gestionnaires de disposition de Swing sont un autre bon exemple, bien que dans ce cas, les choses se soient mal passées, voir Totally GridBag pour une illustration amusante.

NB: Deux modèles sont à l’œuvre ici, l’emballage des flux en flux étant un exemple de Décorateur .

Il y a une différence entre stratégie et décision / choix. La plupart du temps, nous gérions les décisions / choix dans notre code et les réalisions à l'aide de constructions if () / switch (). Le modèle de stratégie est utile lorsqu'il est nécessaire de découpler la logique / algorithme de l'utilisation.

Par exemple, imaginez un mécanisme d’interrogation dans lequel différents utilisateurs vérifieraient les ressources / mises à jour. Maintenant, nous voudrons peut-être que certains des utilisateurs privilégiés soient informés avec un délai d'exécution plus court ou avec plus de détails. Essentailly la logique utilisée change en fonction des rôles des utilisateurs. La stratégie a du sens du point de vue de la conception et de l’architecture, elle devrait toujours être remise en question aux niveaux de granularité les plus bas.

Le modèle de stratégie vous permet d’exploiter le polimorphisme sans étendre votre classe principale. Essentiellement, vous mettez toutes les parties variables dans l'interface de stratégie et les implémentations, et la classe principale les leur délègue. Si votre objet principal utilise une seule stratégie, c'est presque la même chose que d'avoir une méthode abstraite (virtuelle pure) et des implémentations différentes dans chaque sous-classe.

L’approche stratégique présente certains avantages:

  • vous pouvez changer de stratégie au moment de l'exécution - comparez cela au changement du type de classe au moment de l'exécution, ce qui est beaucoup plus difficile, spécifique au compilateur et impossible pour les méthodes non virtuelles
  • une classe principale peut utiliser plusieurs stratégies, ce qui vous permet de les recombiner de plusieurs façons. Prenons une classe qui parcourt une arborescence et évalue une fonction en fonction de chaque nœud et du résultat actuel. Vous pouvez avoir une stratégie de marche (profondeur d'abord ou largeur d'abord) et une stratégie de calcul (certains foncteurs, c'est-à-dire «compter les nombres positifs» ou «somme»). Si vous n'utilisez pas de stratégies, vous devrez implémenter une sous-classe pour chaque combinaison marche / calcul.
  • le code est plus facile à gérer car la stratégie de modification ou de compréhension ne vous oblige pas à comprendre tout l'objet principal

L’inconvénient est que, dans de nombreux cas, le modèle de stratégie est excessif - l’opérateur switch / case est là pour une raison. Envisagez de commencer par de simples instructions de flux de contrôle (commutateur / casse ou si), puis, si nécessaire, passez à la hiérarchie des classes et, si vous avez plusieurs dimensions de variabilité, extrayez les stratégies de celle-ci. Les pointeurs de fonction se situent quelque part au milieu de ce continuum.

Lectures recommandées:

L’une des façons de procéder consiste à effectuer diverses actions et à les déterminer au moment de l’exécution. Si vous créez une table de hachage ou un dictionnaire de stratégies, vous pouvez récupérer les stratégies correspondant aux valeurs de commande ou aux paramètres. Une fois votre sous-ensemble sélectionné, vous pouvez simplement parcourir la liste des stratégies et les exécuter successivement.

Un exemple concret serait le calcul du total d’une commande. Vos paramètres ou commandes sont les suivants: prix de base, taxe locale, taxe de séjour, taxe locale, frais d’envoi et rabais. La flexibilité entre en jeu lorsque vous gérez la variation des commandes - certains États n’auront pas de taxe de vente, tandis que d’autres commandes devront appliquer un coupon. Vous pouvez affecter dynamiquement l'ordre des calculs. Tant que vous avez comptabilisé tous vos calculs, vous pouvez gérer toutes les combinaisons sans recompiler.

Ce modèle de conception permet d'encapsuler des algorithmes dans des classes.

La classe qui utilise la stratégie, la classe client, est découplée de la mise en oeuvre de l'algorithme. Vous pouvez modifier la mise en œuvre des algorithmes ou ajouter un nouvel algorithme sans avoir à modifier le client. Cela peut aussi être fait dynamiquement: le client peut choisir l'algorithme qu'il utilisera.

Par exemple, imaginez une application qui doit enregistrer une image dans un fichier. l'image peut être sauvegardée dans différents formats (PNG, JPG ...). Les algorithmes de codage seront tous implémentés dans différentes classes partageant la même interface. La classe de client en choisira une en fonction des préférences de l'utilisateur.

Dans l'exemple de Wikipedia, ces instances peuvent être transmises à une fonction qui ne doit pas se soucier de la classe à laquelle appartiennent ces instances. La fonction appelle simplement execute sur l'objet transmis et sait que la bonne chose va se passer.

Un exemple typique du modèle de stratégie est le fonctionnement des fichiers sous Unix. Avec un descripteur de fichier, vous pouvez le lire, l'écrire, le consulter, le rechercher, lui envoyer des ioctl , etc., sans avoir à savoir s'il s'agit d'un fichier. , répertoire, pipe, socket, périphérique, etc. (Bien sûr, certaines opérations, comme la recherche, ne fonctionnent pas sur les pipes et les sockets. Mais les lectures et écritures fonctionneront très bien dans ces cas.)

Cela signifie que vous pouvez écrire du code générique pour gérer tous ces différents types de "fichiers", sans avoir à écrire un code séparé pour gérer les fichiers par rapport aux répertoires, etc. Le noyau Unix prend en charge la délégation des appels au code approprié. .

Maintenant, il s'agit du modèle de stratégie tel qu'il est utilisé dans le code du noyau, mais vous n'avez pas précisé qu'il devait s'agir d'un code utilisateur, mais simplement d'un exemple concret. : -)

Le modèle de stratégie fonctionne sur une idée simple, à savoir "Favoriser la composition par rapport à l'héritage". afin que cette stratégie / algorithme puisse être modifié au moment de l'exécution. Pour illustrer notre propos, prenons un exemple dans lequel nous devons crypter différents messages en fonction de leur type, par exemple. MailMessage, ChatMessage, etc.

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();
    }
};

Désormais, au moment de l'exécution, vous pouvez instancier différents messages hérités de CMessage (comme CMailMessage: CMessage public) avec différents crypteurs (comme CDESEncryptor: Public CEncryptor)

CMessage *ptr = new CMailMessage();
ptr->setEncryptor(new CDESEncrypto());
ptr->performEncryption();
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top