Mais la méthode draw () ne dépend-elle pas beaucoup de l'interface utilisateur?
D'un point de vue pragmatique, du code dans votre système doit savoir comment dessiner quelque chose comme un Rectangle
si c'est une exigence de l'utilisateur. Et cela va se résumer à un moment donné à faire des choses de très bas niveau comme la pixellisation des pixels ou l'affichage de quelque chose dans une console.
Du point de vue du couplage, la question est de savoir qui / quoi devrait dépendre de ce type d'informations et à quel degré de détail (dans quelle mesure, par exemple, abstrait)?
Résumé des capacités de dessin / rendu
Parce que si le code de dessin de niveau supérieur ne dépend que de quelque chose de très abstrait, cette abstraction pourrait fonctionner (par substitution d'implémentations concrètes) sur toutes les plates-formes que vous avez l'intention de cibler. À titre d'exemple artificiel, une IDrawer
interface très abstraite peut être capable d'être implémentée dans la console et les API GUI pour faire des choses comme des formes de tracé (l'implémentation de la console peut traiter la console comme une "image" 80xN avec l'art ASCII). Bien sûr, c'est un exemple artificiel car ce n'est généralement pas ce que vous voulez faire, c'est traiter une sortie de console comme un tampon image / frame; généralement, la plupart des besoins des utilisateurs exigent davantage d'interactions textuelles dans les consoles.
Une autre considération est à quel point est-il facile de concevoir une abstraction stable? Parce que cela pourrait être facile si tout ce que vous visez est des API GUI modernes pour résumer les capacités de base de dessin de formes comme les lignes de tracé, les rectangles, les chemins, le texte, les choses de ce genre (juste une rastérisation 2D simple d'un ensemble limité de primitives) , avec une interface abstraite qui peut être facilement implémentée pour eux tous à travers différents sous-types à peu de frais. Si vous pouvez concevoir une telle abstraction efficacement et l'implémenter sur toutes les plates-formes cibles, je dirais que c'est un mal bien moindre, voire un mal du tout, pour une forme ou un contrôle graphique ou quoi que ce soit pour savoir comment se dessiner en utilisant un tel abstraction.
Mais disons que vous essayez d'abstraire les détails sanglants qui varient entre une Playstation Portable, un iPhone, une XBox One et un PC de jeu puissant tandis que vos besoins sont d'utiliser les techniques de rendu / d'ombrage 3D en temps réel les plus avancées sur chacun . Dans ce cas, essayer de proposer une interface abstraite pour résumer les détails de rendu lorsque les capacités matérielles et les API sous-jacentes varient si énormément est presque certain de générer un temps énorme de conception et de reconception, une forte probabilité de modifications de conception récurrentes avec des imprévus découvertes, et également une solution de dénominateur commun le plus bas qui ne parvient pas à exploiter le caractère unique et la puissance du matériel sous-jacent.
Faire passer les dépendances vers des conceptions stables et "faciles"
Dans mon domaine, je suis dans ce dernier scénario. Nous ciblons de nombreux matériels différents avec des capacités et des API sous-jacentes radicalement différentes, et essayer de proposer une seule abstraction de rendu / dessin pour les gouverner tous est sans espoir (nous pourrions devenir mondialement célèbre en le faisant efficacement car ce serait un jeu). changeur dans l'industrie). Donc, la dernière chose que je veux dans mon cas est comme l'analogique Shape
ou Model
ou Particle Emitter
qui sait se dessiner, même s'il exprime ce dessin de la manière la plus élevée et la plus abstraite possible ...
... parce que ces abstractions sont trop difficiles à concevoir correctement, et quand un design est difficile à obtenir correctement, et que tout en dépend, c'est une recette pour les changements de conception centrale les plus coûteux qui ondulent et cassent tout en fonction. Donc, la dernière chose que vous voulez, c'est que les dépendances de vos systèmes se dirigent vers des conceptions abstraites trop difficiles à obtenir (trop difficiles à stabiliser sans changements intrusifs).
Difficile dépend de facile, pas facile dépend de difficile
Donc, ce que nous faisons à la place, c'est que les dépendances se dirigent vers des choses faciles à concevoir. Il est beaucoup plus facile de concevoir un "modèle" abstrait qui se concentre uniquement sur le stockage de choses comme les polygones et les matériaux et d'obtenir ce design correct que de concevoir un "rendu" abstrait qui peut être efficacement mis en œuvre (via des sous-types concrets substituables) pour dessiner le dessin. demande uniformément un matériel aussi disparate qu'une PSP à partir d'un PC.
Nous inversons donc les dépendances loin des choses difficiles à concevoir. Au lieu de faire en sorte que les modèles abstraits sachent se dessiner sur un design de rendu abstrait dont ils dépendent tous (et casser leurs implémentations si ce design change), nous avons plutôt un rendu abstrait qui sait comment dessiner chaque objet abstrait de notre scène ( modèles, émetteurs de particules, etc.), et ainsi nous pouvons ensuite implémenter un sous-type de rendu OpenGL pour les PC comme RendererGl
, un autre pour les PSP comme RendererPsp
, un autre pour les téléphones mobiles, etc. Dans ce cas, les dépendances se dirigent vers des conceptions stables, faciles à corriger, du rendu à différents types d'entités (modèles, particules, textures, etc.) dans notre scène, et non l'inverse.
- J'utilise "stabilité / instabilité" dans un sens légèrement différent de la métrique des couplages afférents / efférents de l'oncle Bob qui mesure plus la difficulté du changement pour autant que je puisse comprendre. Je parle plus de "probabilité d'exiger un changement", bien que sa métrique de stabilité y soit utile. Lorsque la «probabilité de changement» est proportionnelle à la «facilité de changement» (ex: les éléments les plus susceptibles de nécessiter des changements présentent l'instabilité la plus élevée et les couplages afférents de la métrique de l'oncle Bob), alors de tels changements probables sont bon marché et non intrusifs à effectuer. , ne nécessitant que le remplacement d'une implémentation sans toucher à aucune conception centrale.
Si vous essayez d'abstraire quelque chose au niveau central de votre base de code et que c'est trop difficile à concevoir, au lieu de vous battre obstinément la tête contre les murs et d'y apporter constamment des changements intrusifs chaque mois / année, ce qui nécessite la mise à jour de 8000 fichiers source car c'est briser tout ce qui en dépend, ma suggestion numéro un est d'envisager d'inverser les dépendances. Voyez si vous pouvez écrire le code d'une manière telle que la chose qui est si difficile à concevoir dépend de tout le reste qui est plus facile à concevoir, ne pas avoir les choses qui sont plus faciles à concevoir en fonction de la chose qui est si difficile à concevoir. Notez que je parle de conceptions (en particulier de conceptions d'interfaces) et non d'implémentations: parfois les choses sont faciles à concevoir et difficiles à implémenter, et parfois les choses sont difficiles à concevoir mais faciles à mettre en œuvre. Les dépendances se déplacent vers les conceptions, donc l'accent ne devrait être mis que sur la difficulté de concevoir quelque chose ici pour déterminer la direction dans laquelle les dépendances se déplacent.
Principe de responsabilité unique
Pour moi, SRP n'est pas si intéressant ici habituellement (bien que cela dépende du contexte). Je veux dire qu'il y a un acte d'équilibre sur la corde raide dans la conception de choses claires dans leur objectif et maintenables, mais vos Shape
objets peuvent avoir à exposer des informations plus détaillées s'ils ne savent pas comment se dessiner, par exemple, et il peut ne pas y avoir beaucoup de choses significatives à faire avec une forme dans un contexte d'utilisation particulier que de la construire et de la dessiner. Il y a des compromis avec à peu près tout, et ce n'est pas lié à la SRP qui peut rendre les choses conscientes de la façon de se dessiner capables de devenir un tel cauchemar de maintenance dans mon expérience dans certains contextes.
Cela a beaucoup plus à voir avec le couplage et la direction dans laquelle les dépendances circulent dans votre système. Si vous essayez de porter une interface de rendu abstraite dont tout dépend (car ils l'utilisent pour se dessiner) vers une nouvelle API / matériel cible et que vous réalisez que vous devez modifier considérablement sa conception pour qu'elle fonctionne efficacement là-bas, alors c'est un changement très coûteux à effectuer qui nécessite de remplacer les implémentations de tout ce qui sait se dessiner dans votre système. Et c'est le problème de maintenance le plus pratique que je rencontre avec des choses qui savent comment se dessiner si cela se traduit par une charge de dépendances qui coule vers des abstractions qui sont trop difficiles à concevoir correctement à l'avance.
Développeur Pride
Je mentionne ce point parce que, selon mon expérience, c'est souvent le plus grand obstacle à la direction des dépendances vers des choses plus faciles à concevoir. Il est très facile pour les développeurs de devenir un peu ambitieux ici et de dire: "Je vais concevoir l'abstraction de rendu multiplateforme pour les gouverner tous, je vais résoudre ce que les autres développeurs passent des mois à porter, et je vais obtenir il va bien et ça va fonctionner comme par magie sur chaque plate-forme unique que nous prenons en charge et utiliser les techniques de rendu de pointe sur chacun; je l'ai déjà imaginé dans ma tête. "Dans ce cas, ils résistent à la solution pratique qui consiste à éviter de faire cela et à inverser simplement la direction des dépendances et à traduire ce qui pourrait être extrêmement coûteux et les modifications récurrentes de la conception centrale en des changements récurrents simplement bon marché et locaux à la mise en œuvre. Il doit y avoir une sorte d'instinct de "drapeau blanc" chez les développeurs pour abandonner quand quelque chose est trop difficile à concevoir à un niveau aussi abstrait et reconsidérer toute leur stratégie, sinon ils sont dans une situation de chagrin et de douleur. Je suggérerais de transférer de telles ambitions et un esprit combatif vers des implémentations de pointe d'une chose plus facile à concevoir que de prendre de telles ambitions de conquête du monde au niveau de la conception d'interface.