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 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 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?
N'importe lequel des modèles de corps de poignée pourrait 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.
Enquêter sur le concept de réutilisation horizontale , où vous pouvez trouver des choses telles que Traits , le toujours expérimental mais déjà à l'épreuve de la production Programmation orientée aspect et parfois haï - Mixins .
Un moyen direct d'ajouter des méthodes à une classe dépend également du langage de programmation. Ruby permet pour patch de singe tandis que Javascript est basé sur un prototype héritage, 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 .
S'il existe une exigence selon laquelle l'instance bakery
doit changer son comportement dynamiquement (en fonction des actions de l'utilisateur, etc.), alors vous devriez opter pour le modèle de décorateur.
Si bakery
ne change pas son comportement dynamiquement mais que vous ne pouvez pas modifier le Bakery class
(API externe, etc.), alors vous devriez opter pour le modèle d'adaptateur.
Si bakery
ne change pas son comportement dynamiquement et que vous pouvez modifier Bakery class
alors vous devez étendre l'interface existante (comme vous l'avez initialement proposé) ou introduire une nouvelle interfaceBrownieInterface
et laisser Bakery
implémenter deux interfaces BakeryInterface
et BrownieInterface
.
Sinon, vous allez ajouter une complexité inutile à votre code (en utilisant le modèle Decorator) sans raison valable!
Vous avez rencontré le problème d'expression. Cet article de Stuart Sierra traite de la solution des clojures, mais il donne également un exemple dans Java et répertorie les solutions populaires en OO classique.
Il y a aussi cette présentation de Chris Houser qui traite à peu près du même terrain.