Motif décorateur contre héritage?

j'ai lu décorateur à partir de Wikipedia, et l'exemple de code à partir de ce site.

je vois le point que l'héritage traditionnel suit un modèle "est-a" tandis que le décorateur suit un modèle "a-a". Et la convention d'appel de décorateur ressemble à une "peau" plus de "peau".. au cours "de base". par exemple,

I* anXYZ = new Z( new Y( new X( new A ) ) );

comme démontré dans le lien d'exemple de code ci-dessus.

cependant il y a encore quelques questions que je ne comprends pas:

  1. que signifie wiki par ' le motif decorator peut être utilisé pour étendre (décorer) la fonctionnalité d'un certain objet au moment de l'exécution'? le 'nouveau ...(nouveau... (nouveau...))' est un appel et est bon, mais un 'AwithXYZ anXYZ;' est un héritage au moment de la compilation et de l'est mauvais?

  2. à partir du lien exemple de code je peux voir que le nombre de définition de classe est presque le même dans les deux application. Je me souviens dans d'autres livres de modèle de conception comme "la tête d'abord modèles de conception". Ils utilisent le café starbuzz comme exemple et disent que l'héritage traditionnel causera un 'classe explosion' parce que pour chaque combinaison de café, vous arriveriez avec un cours pour elle.

    mais n'est-ce pas la même chose pour décorateur dans ce cas? Si une classe de décorateur peut prendre N'importe quelle classe abstraite et la décorer, alors je suppose qu'elle empêche l'explosion, mais de l'exemple de code, vous exacte # de définitions de classe, pas moins...

quelqu'un pourrait-il m'expliquer?

23
demandé sur halfer 2012-09-12 04:47:43

5 réponses

prenons quelques flux abstraits par exemple et imaginons que vous voulez fournir des services de cryptage et de compression sur eux.

Avec le décorateur vous avez des (pseudo-code):

Stream plain = Stream();
Stream encrypted = EncryptedStream(Stream());
Stream zipped = ZippedStream(Stream());
Stream zippedEncrypted = ZippedStream(EncryptedStream(Stream());
Stream encryptedZipped = EncryptedStream(ZippedStream(Stream());

Avec l'héritage, vous avez:

class Stream() {...}
class EncryptedStream() : Stream {...}
class ZippedStream() : Stream {...}
class ZippedEncryptedStream() : EncryptedStream {...}
class EncryptedZippedStream() : ZippedStream {...}

1) avec decorator, vous combinez la fonctionnalité à l'exécution, en fonction de vos besoins. Chaque classe ne s'occupe que d'une seule facette de fonctionnalité (compression, cryptage,...)...)

2) dans cet exemple simple, nous avons 3 classes avec décorateurs, et 5 avec héritage. Maintenant, ajoutons quelques services supplémentaires, par exemple le filtrage et le découpage. Avec decorator vous n'avez besoin que de 2 classes supplémentaires pour prendre en charge tous les scénarios possibles, par exemple filtrage -> clipping -> compression -> encription. Avec l'héritage, vous devez fournir une classe pour chaque combinaison si vous vous retrouvez avec des dizaines de classes.

69
répondu Zdeslav Vojkovic 2014-01-10 08:54:21

Dans l'ordre inverse:

2) Avec, disons, 10 indépendant extensions, n'importe quelle combinaison de ce qui pourrait être nécessaire au moment de l'exécution, 10 classes de décorateur feront le travail. Pour couvrir toutes les possibilités par héritage vous avez besoin 1024 sous-classes. Et il n'y aurait aucun moyen de contourner la redondance massive des codes.

1) Imaginez que vous aviez ces sous-classes 1024 à choisir à l'exécution. Essayez d'écrire le code qui serait nécessaire. Garder à l' l'esprit que vous pourriez ne pas être en mesure de dicter l'ordre dans lequel les options sont choisi ou rejeté. Rappelez-vous aussi que vous pourriez avoir à utiliser une instance pendant un certain temps avant de l'étendre. Allez-y, essayez. Le faire avec des décorateurs est insignifiant en comparaison.

7
répondu Beta 2012-09-12 01:05:03

Vous avez raison qu'ils peuvent être très semblables à la fois. L'applicabilité et les avantages de l'une ou l'autre solution dépendra de votre situation.

D'autres m'ont devancé pour obtenir des réponses adéquates à votre deuxième question. Bref, c'est que vous pouvez combiner les décorateurs d'obtenir plus de combinaisons que vous ne pouvez pas faire avec l'héritage.

en tant que tel je me concentre sur le premier:

vous ne pouvez pas dire strictement que le temps de compilation est mauvais et le temps d'exécution est bon, c'est juste différent flexibilité. La possibilité de changer les choses à l'exécution peut être importante pour certains projets car elle permet des changements sans recompilation qui peuvent être lents et nécessitent que vous soyez dans un environnement où vous pouvez compiler.

un exemple où vous ne pouvez pas utiliser l'héritage, est quand vous voulez ajouter une fonctionnalité à un objet instancié. Supposons que vous ayez une instance d'un objet qui implémente une interface de journalisation:

public interface ILog{
    //Writes string to log
    public void Write( string message );
}

supposons maintenant que vous commenciez un tâche qui implique de nombreux objets et chacun d'eux ne journalisation si vous passez le long de la journalisation de l'objet. Cependant, vous voulez que chaque message de la tâche soit préfixé avec le nom de la tâche et L'Id de la tâche. Vous pouvez passer une fonction, ou passer le nom et L'Id et faire confiance à chaque appelant pour suivre la règle de pré-attente de cette information, ou vous pouvez décorer l'objet de journalisation avant de le passer et ne pas avoir à vous soucier des autres objets le faisant correctement

public class PrependLogDecorator : ILog{

     ILog decorated;

     public PrependLogDecorator( ILog toDecorate, string messagePrefix ){
        this.decorated = toDecorate;
        this.prefix = messagePrefix;
     }

     public void Write( string message ){
        decorated.Write( prefix + message );
     }
}

Désolé le code C# mais je pense qu'il va quand même communiquer les idées à quelqu'un qui connait le c++

3
répondu vossad01 2012-09-12 01:18:30

pour répondre À la deuxième partie de votre question (qui pourrait tourner à l'adresse de votre première partie), à l'aide de la décoratrice méthode que vous avez accès au même nombre de combinaisons, mais ne pas avoir à les écrire. Si vous avez 3 couches de décorateurs avec 5 options à chaque niveau, vous avez 5*5*5 classes possibles à définir en utilisant l'héritage. En utilisant la méthode de décorateur vous avez besoin de 15.

1
répondu tacaswell 2012-09-12 00:53:29

tout d'abord, je suis une personne C# et je n'ai pas eu affaire à C++ depuis un moment, mais j'espère que vous obtenez là d'où je viens.

Un bon exemple qui vient à l'esprit est un DbRepository et CachingDbRepository:

public interface IRepository {
  object GetStuff();
}

public class DbRepository : IRepository {

  public object GetStuff() {
    //do something against the database
  }
}

public class CachingDbRepository : IRepository {
  public CachingDbRepository(IRepository repo){ }

  public object GetStuff() {
    //check the cache first
    if(its_not_there) {
      repo.GetStuff();
    }
}

donc, si j'utilisais l'héritage, j'aurais un DbRepository et CachingDbRepository. DbRepository requête à partir d'une base de données;CachingDbRepository vérifierait son cache et si les données n'étaient pas là, il interrogerait une base de données. Donc il y a une possible duplication de la mise en œuvre ici.

En utilisant le pattern décorateur, j'ai toujours le même nombre de classes, mais mon CachingDbRepository a la une IRepository et son GetStuff() pour récupérer les données du fichier repo sous-jacent si elles ne sont pas dans le cache.

Donc le nombre de classes sont les mêmes, mais l'utilisation de classes sont liées. CachingDbRepo appelle la pension qui y a été passée...donc c'est plus comme la composition sur l'héritage.

je trouve subjectif quand à décider quand utiliser juste l'héritage avant la décoration.

j'espère que cela vous aidera. Bonne chance!

1
répondu David Hoerster 2012-09-12 01:01:24