Comment dois-je ajouter des fonctionnalités à un objet qui existe déjà?

25

J'ai une interface qui a une certaine quantité de fonctionnalités bien définies. Disons:

interface BakeryInterface {
  public function createCookies();
  public function createIceCream();
}

Cela fonctionne bien pour la plupart des implémentations de l'interface, mais dans quelques cas, j'ai besoin d'ajouter de nouvelles fonctionnalités (comme peut-être intégrées dans une nouvelle méthode createBrownies()). L'approche évidente / naïve pour ce faire serait d'étendre l'interface:

interface BrownieBakeryInterface extends BakeryInterface {
  public function createBrownies();
}

Mais a un assez gros inconvénient en ce que je ne peux pas ajouter la nouvelle fonctionnalité sans modifier l'API existante (comme changer la classe pour utiliser la nouvelle interface).

Je pensais utiliser un adaptateur pour ajouter la fonctionnalité après l'instanciation:

class BrownieAdapter {
  private brownieBakery;

  public function construct(BakeryInterface bakery) {
    this->brownieBakery = bakery;
  }

  public function createBrownies() {
    /* ... */
  }
}

Ce qui me rapporterait quelque chose comme:

bakery = new Bakery();
bakery = new BrownieBakery(bakery);
bakery->createBrownies();

Cela semble être une bonne solution au problème, mais je me demande si j'éveille les anciens dieux en le faisant. L'adaptateur est-il la voie à suivre? Y a-t-il un meilleur schéma à suivre? Ou dois-je vraiment mordre la balle et étendre simplement l'interface d'origine?


la source
Delphi a des classes auxiliaires, c'est comme ajouter des méthodes aux classes existantes sans vraiment les modifier. Par exemple, Delphi a une classe TBitmap définie dans son unité graphique, vous pouvez créer une classe d'assistance qui ajoute, par exemple, une fonction Flip à TBitmap. Tant que la classe d'assistance est dans la portée, vous pouvez appeler MyBitmap.Flip;
Bill

Réponses:

14

Tous les modèles de corps de poignée peuvent correspondre à la description, en fonction de vos exigences exactes, de votre langue et du niveau d'abstraction requis.

L'approche puriste serait le modèle Décorateur , qui fait exactement ce que vous recherchez, ajoute dynamiquement des responsabilités aux objets. Si vous construisez des boulangeries, c'est définitivement exagéré et vous devriez simplement aller avec l'adaptateur.

yannis
la source
C'est exactement ce dont j'avais besoin: j'ai réalisé que l'utilisation d'un adaptateur viserait avec l'injection de dépendance, mais l'utilisation d'un décorateur contourne cela.
5

Enquêter sur le concept de réutilisation horizontale , où vous pouvez trouver des trucs comme Traits , la programmation orientée aspect encore expérimentale mais déjà à l'épreuve de la production et les Mixins parfois détestés .

Un moyen direct d'ajouter des méthodes à une classe dépend également du langage de programmation. Ruby permet le patch de singe tandis que l' héritage basé sur un prototype de Javascript , où les classes n'existent pas vraiment, vous créez un objet et vous le copiez simplement et continuez à y ajouter, par exemple:

var MyClass = {
    do : function(){...}
};

var MyNewClass = new MyClass;
MyClass.undo = function(){...};


var my_new_object = new MyNewClass;
my_new_object.do();
my_new_object.undo();

Enfin, vous pouvez également émuler la réutilisation horizontale, ou la «modification» et l '«ajout» à l'exécution du comportement de classe / objet avec réflexion .

dukeofgaming
la source
4

S'il existe une exigence selon laquelle l' bakeryinstance doit changer son comportement de manière dynamique (en fonction des actions de l'utilisateur, etc.), vous devez opter pour le modèle Décorateur .

Si bakeryne change pas son comportement de manière dynamique mais que vous ne pouvez pas modifier Bakery class(API externe, etc.), vous devez opter pour le modèle d'adaptateur .

Si bakeryne change pas son comportement dynamiquement et que vous pouvez le modifier, Bakery classvous devez étendre l'interface existante (comme vous l'avez initialement proposé) ou introduire une nouvelle interface BrownieInterface et laisser Bakeryimplémenter deux interfaces BakeryInterfaceet BrownieInterface.
Sinon, vous allez ajouter une complexité inutile à votre code (en utilisant le modèle Decorator) sans raison valable!

Dîme
la source