Il y a pas mal de questions similaires 1 ,2 ,3 ,4 , mais non ne semble pas exactement le cas dans cette question, et les solutions ne semblent pas optimales non plus.
Il s'agit d'une question OOP générale, en supposant que le polymorphisme, les génériques et les mixins sont disponibles. Le langage réel à utiliser est OOP Javascript (Typescript), mais c'est le même problème en Java ou C ++.
J'ai des hiérarchies de classes parallèles, qui partagent parfois le même comportement (interface et implémentation), mais parfois chacune a son propre comportement «protégé». Illustré comme ceci:
Ceci est uniquement à des fins d'illustration ; ce n'est pas le diagramme de classe réel. Pour le lire:
- Tout élément de la hiérarchie commune (centre) est partagé entre les hiérarchies Canvas (gauche) et SVG (droite). Par partage, j'entends à la fois l'interface et la mise en œuvre.
- Rien que dans les colonnes de gauche ou de droite signifie un comportement (méthodes et membres) spécifique à cette hiérarchie. Par exemple:
- Les hiérarchies gauche et droite utilisent exactement les mêmes mécanismes de validation, présentés comme une méthode unique (
Viewee.validate()
) dans la hiérarchie commune. - Seule la hiérarchie du canevas a une méthode
paint()
. Cette méthode appelle la méthode paint sur tous les enfants. - La hiérarchie SVG doit remplacer la
addChild()
méthode deComposite
, mais ce n'est pas le cas avec la hiérarchie du canevas.
- Les hiérarchies gauche et droite utilisent exactement les mêmes mécanismes de validation, présentés comme une méthode unique (
- Les constructions des hiérarchies des deux côtés ne peuvent pas être mélangées. Une usine assure cela.
Solution I - Tease Apart Héritage
Fowler's Tease Apart Inheritance ne semble pas faire l'affaire ici, car il y a une certaine différence entre les deux parallèles.
Solution II - Mixins
C'est le seul auquel je pense actuellement. Les deux hiérarchies sont développées séparément, mais à chaque niveau les classes se mélangent dans la classe commune, qui ne font pas partie d'une hiérarchie de classe. Omettre la structural
fourche, ressemblera à ceci:
Notez que chaque colonne sera dans son propre espace de noms, donc les noms de classe ne seront pas en conflit.
La question
Quelqu'un peut-il voir les défauts de cette approche? Quelqu'un peut-il penser à une meilleure solution?
Addenda
Voici un exemple de code d'utilisation. L'espace de noms svg
peut être remplacé par canvas
:
var iView = document.getElementById( 'view' ),
iKandinsky = new svg.Kandinsky(),
iEpigone = new svg.Epigone(),
iTonyBlair = new svg.TonyBlair( iView, iKandinsky ),
iLayer = new svg.Layer(),
iZoomer = new svg.Zoomer(),
iFace = new svg.Rectangle( new Rect( 20, 20, 100, 60) ),
iEyeL = new svg.Rectangle( new Rect( 20, 20, 20, 20) ),
iEyeR = new svg.Rectangle( new Rect( 60, 20, 20, 20) );
iKandinsky.setContext( iTonyBlair.canvas.getContext( '2d' ) );
iEpigone.setContext( iTonyBlair.canvas.getContext( '2d' ) );
iFace.addChildren( iEyeL, iEyeR );
iZoomer.setZoom( new Point( 2, 2 ) );
iZoomer.addChild( iFace );
iLayer.addChild( iZoomer );
iTonyBlair.setContent( iLayer );
Essentiellement, lors de l' exécution, les clients composent la hiérarchie des instances des sous-classes Viewee; ainsi:
Supposons que tous ces visiteurs proviennent de la hiérarchie du canevas, ils sont rendus en parcourant la hiérarchie peut faire appel paint()
à chaque visiteur. S'ils proviennent de la hiérarchie svg, les visiteurs savent comment s'ajouter au DOM, mais il n'y a pas de paint()
traversée.
Réponses:
La deuxième approche isole mieux les interfaces, en suivant le principe de ségrégation des interfaces.
Cependant, j'ajouterais une interface Paintable.
Je changerais également certains noms. Pas besoin de créer de confusion:
la source
Ce n'est en fait pas vrai du tout. Le typage a un avantage substantiel sur Java, à savoir le typage structurel. Vous pouvez faire quelque chose de similaire en C ++ avec des modèles de type canard, mais c'est beaucoup plus d'efforts.
Fondamentalement, définissez vos classes, mais ne vous embêtez pas à étendre ou à définir des interfaces. Définissez ensuite simplement l'interface dont vous avez besoin et prenez-la comme paramètre. Ensuite, les objets peuvent correspondre à cette interface - ils n'ont pas besoin de savoir pour l'étendre à l'avance. Chaque fonction peut déclarer exactement et uniquement les bits pour lesquels elle donne une merde, et le compilateur vous donnera une passe si le type final le rencontre, même si les classes n'étendent pas réellement ces interfaces explicitement.
Cela vous libère de la nécessité de définir réellement une hiérarchie d'interface et de définir quelles classes doivent étendre quelles interfaces.
Définissez simplement chaque classe et oubliez les interfaces - le typage structurel s'en chargera.
Par exemple:
C'est typographique totalement légitime. Notez que les fonctions consommatrices ne connaissent pas ou ne donnent pas une seule merde sur les classes de base ou les interfaces utilisées dans la définition des classes.
Peu importe que les classes soient liées ou non par héritage. Peu importe s'ils ont étendu votre interface. Définissez simplement l'interface comme paramètre, et vous avez terminé - toutes les classes qui y répondent sont acceptées.
la source
validate()
méthode (dont l'implémentation est identique pour les deux); alors seulement svg.Viewee a besoin de remplacer addChild () , tandis que seulement canvas.Viewee a besoin de paint () (qui appelle paint () sur tous les enfants - qui sont membres de la classe Composite de base ). Je ne peux donc pas vraiment visualiser cela avec un typage structurel.SVGViewee.addChild()
, mais avezCanvasViewee
également besoin exactement de la même fonctionnalité. Il me semble donc logique que les deux soient inhérents au composite?Rapide vue d'ensemble
Solution 3: le modèle de conception de logiciel "Hiérarchie de classes parallèles" est votre ami.
Réponse longue et étendue
Votre conception A COMMENCÉ À DROITE. Il peut être optimisé, certaines classes ou certains membres peuvent être supprimés, mais l'idée de «hiérarchie parallèle» que vous appliquez pour résoudre un problème EST DROITE.
Traitez plusieurs fois le même concept, généralement dans les hiérarchies de contrôle.
Après un certain temps, J'AI FINI DE FAIRE LA MÊME SOLUTION QUE LES AUTRES DÉVELOPPEURS, ce qu'on appelle parfois le modèle de conception "Hiérarchie parallèle" ou le modèle de conception "Double hiérarchie".
(1) Avez-vous déjà divisé une seule classe en une seule hiérarchie de classes?
(2) Avez-vous déjà divisé une seule classe en plusieurs classes, sans hiérarchie?
Si vous avez appliqué ces solutions précédentes séparément, elles sont un moyen de résoudre certains problèmes.
Mais que se passe-t-il si nous combinons ces deux solutions simultanément?
Combinez-les et vous obtiendrez ce "modèle de conception".
la mise en oeuvre
Maintenant, appliquons le modèle de conception de logiciel "Hiérarchie de classes parallèles" à votre cas.
Vous avez actuellement 2 hiérarchies de classes indépendantes ou plus, qui sont très similaires, ont des associations ou purpouse similaires, ont des propriétés ou des méthodes similaires.
Vous souhaitez éviter d'avoir du code ou des membres en double ("cohérence"), mais vous ne pouvez pas fusionner directement ces classes en une seule, en raison des différences entre elles.
Donc, vos hiérarchies sont très similaires à ce chiffre, mais, pourtant, il y en a plus d'un:
Dans ce modèle de conception non encore certifié, PLUSIEURS HIÉRARCHIES SIMILAIRES SONT FUSIONNÉES, EN UNE HIERARCHIE UNIQUE, et chaque classe partagée ou commune est étendue par sous-classement.
Notez que cette solution est complexe, car vous avez déjà affaire à plusieurs hiérarchies, c'est donc un scénario complexe.
1 La classe racine
Dans chaque hiérarchie, il existe une classe "racine" partagée.
Dans votre cas, il existe une classe "Composite" indépendante, pour chaque hiérarchie, qui peut avoir des propriétés similaires et des méthodes similaires.
Certains de ces membres peuvent être fusionnés, certains de ces membres ne peuvent pas être fusionnés.
Ainsi, ce qu'un développeur peut faire, c'est de créer une classe racine de base et de sous-classer le cas équivalent pour chaque hiérarchie.
Dans la figure 2, vous pouvez voir un diagramme uniquement pour cette classe, dans lequel chaque classe conserve son espace de noms.
Les membres sont désormais omis.
Comme vous pouvez le remarquer, chaque classe "Composite" n'est plus dans une hiérarchie distincte, mais fusionnée en une seule hiérarchie partagée ou commune.
Ensuite, ajoutons les membres, ceux qui sont les mêmes, peuvent être déplacés vers la superclasse, et ceux qui sont différents, dans chaque classe de base.
Et comme vous le savez déjà, les méthodes "virtuelles" ou "surchargées" sont définies dans la classe de base, mais remplacées dans les sous-classes. Comme la figure 3.
Notez qu'il existe peut-être des classes sans membres et, vous pourriez être tenté de supprimer ces classes, DONT. Ils sont appelés "classes creuses", "classes énumératives" et autres noms.
2 Les sous-classes
Revenons au premier diagramme. Chaque classe "Composite", avait une sous-classe "Viewee", dans chaque hiérarchie.
Le processus est répété pour chaque classe. Notez que la figure 4, la classe "Common :: Viewee" descend de la "Common :: Composite", mais, pour simplifier, la classe "Common :: Composite" est omise du diagramme.
Vous remarquerez que "Canvas :: Viewee" et "SVG :: Viewee", NE DESCEND PAS PLUS LONGTEMPS de leur "Composite" respectif, mais, du "Common :: Viewee" commun, à la place.
Vous pouvez maintenant ajouter les membres.
3 Répétez le processus
Le processus continuera, pour chaque classe, "Canvas :: Visual" ne descendra pas de "Canvas :: Viewee", buit de "Commons :: Visual", "Canvas :: Structural" ne descendra pas de "Canvas :: Viewee ", extrait de" Commons :: Structural ", et ainsi de suite.
4 Le diagramme de la hiérarchie 3D
Vous finirez d'obtenir une sorte de diagramme 3D, avec plusieurs couches, la couche supérieure, a la hiérarchie "commune" et les couches inférieures, a chaque hiérarchie supplémentaire.
Vos hiérarchies de classes indépendantes d'origine, où quelque chose de similaire à cela (figure 6):
Notez que certaines classes sont omises, et toute la hiérarchie "Canvas" est omise, pour simplifier.
La hiérarchie de classe intégrée finale peut ressembler à ceci:
Notez que certaines classes sont omises, et toutes les classes "Canvas" sont omises, pour simplifier, mais, seront similaires aux classes "SVG".
Les classes "Common" pourraient être représentées comme une seule couche d'un diagramme 3D, les classes "SVG" dans une autre couche et les classes "Canvas", dans une troisième couche.
Vérifiez que chaque couche est liée à la première, dans laquelle chaque classe a une classe parente de la hiérarchie "Common".
L'implémentation du code peut nécessiter l'utilisation de l'héritage d'interface, de l'héritage de classe ou de "mixins", selon ce que votre langage de programmation prend en charge.
Résumé
Comme toute solution de programmation, ne vous précipitez pas dans l'optimisation, l'optimisation est très importante, mais une mauvaise optimisation peut devenir un problème plus important que le problème d'origine.
Je ne recommande pas d'appliquer "Solution 1" ou "Solution 2".
Dans "Solution 1" ne s'applique pas, car l'héritage est requis dans chaque cas.
"Solution 2", "Mixins" peuvent être appliqués, mais, après la conception des classes et des hiérarchies.
Les mixins sont une alternative à l'héritage basé sur une interface ou à l'héritage multiple basé sur une classe.
Ma proposition, la Solution 3, est parfois appelée le modèle de conception "Hiérarchie parallèle" ou le modèle de conception "Hiérarchie double".
De nombreux développeurs / concepteurs ne seront pas d'accord avec cela et pensent qu'il ne devrait pas exister. Mais, j'ai utilisé par moi-même et d'autres développeurs comme une solution commune aux problèmes, comme celui de votre question.
Une autre chose manquante. Dans vos solutions précédentes, le problème principal n'était pas de savoir si utiliser des «mixins» ou des «interfaces», mais, pour affiner, d'abord, le modèle de vos classes, puis utiliser une fonctionnalité de langage de programmation existante.
la source
canvas.viewee
et tous ses descendants ont besoin d'une méthode appeléepaint()
. Ni lecommon
ni lessvg
classes n'en ont besoin. Mais dans votre solution, la hiérarchie est danscommon
, pascanvas
ousvg
comme dans ma solution 2. Alors, commentpaint()
se retrouve- t-il exactement dans toutes les sous-classescanvas.viewee
s'il n'y a pas d'héritage?paint()
doit être déplacé ou déclaré dans "canvas :: viewee". L'idée de modèle général reste, mais certains membres peuvent devoir être déplacés ou modifiés.canvas::viewee
?Dans un article intitulé Design Patterns for Dealing with Dual Inheritance Hierarchies in C ++ , Uncle Bob présente une solution appelée Stairway to Heaven . C'est l'intention déclarée:
Et le diagramme fourni:
Bien que dans la solution 2, il n'y ait pas d'héritage virtuel, il est tout à fait conforme au modèle Stairway to Heaven . La solution 2 semble donc raisonnable pour ce problème.
la source