Modèles de conception à éviter [fermé]


105

Beaucoup de gens semblent d'accord pour dire que le modèle Singleton présente un certain nombre d'inconvénients et certains suggèrent même d'éviter complètement le modèle. Il y a une excellente discussion ici . Veuillez adresser vos commentaires sur le modèle Singleton à cette question.

Ma question : y a-t-il d'autres modèles de conception à éviter ou à utiliser avec beaucoup de soin?


Je dois juste remarquer une excellente liste d'antipatterns de conception deviq.com/antipatterns
Développeur

@casperOne pourquoi avez-vous fermé la question? La question était légitime.
bleepzter

Réponses:


149

Les modèles sont complexes

Tous les modèles de conception doivent être utilisés avec précaution. À mon avis, vous devriez refactoriser vers des modèles lorsqu'il y a une raison valable de le faire au lieu d'implémenter un modèle tout de suite. Le problème général de l'utilisation des modèles est qu'ils ajoutent de la complexité. La surutilisation des modèles rend une application ou un système donné difficile à développer et à maintenir.

La plupart du temps, il existe une solution simple et vous n'aurez pas besoin d'appliquer de modèle spécifique. Une bonne règle de base est d'utiliser un modèle chaque fois que des morceaux de code ont tendance à être remplacés ou doivent être changés souvent et soyez prêt à accepter la mise en garde du code complexe lors de l'utilisation d'un modèle.

N'oubliez pas que votre objectif doit être la simplicité et utiliser un modèle si vous voyez un besoin pratique de prendre en charge le changement dans votre code.

Principes sur les modèles

Il peut sembler inutile d’utiliser des modèles s’ils peuvent de toute évidence conduire à des solutions trop élaborées et complexes. Cependant, il est bien plus intéressant pour un programmeur de se renseigner sur les techniques et principes de conception qui jettent les bases de la plupart des modèles. En fait, l'un de mes livres préférés sur les «modèles de conception» le souligne en rappelant les principes applicables au modèle en question. Ils sont suffisamment simples pour être utiles que les modèles en termes de pertinence. Certains des principes sont suffisamment généraux pour englober plus que la programmation orientée objet (POO), comme le principe de substitution de Liskov , tant que vous pouvez créer des modules de votre code.

Il existe une multitude de principes de conception, mais ceux décrits dans le premier chapitre du livre du GoF sont très utiles pour commencer.

  • Programmez vers une «interface», pas une «implémentation». (Gang of Four 1995: 18)
  • Privilégiez la «composition d'objets» par rapport à «l'héritage de classe». (Gang of Four 1995: 20)

Laissez-les vous envahir pendant un moment. Il convient de noter que lorsque GoF a été écrit, une interface signifie tout ce qui est une abstraction (ce qui signifie également des super classes), à ne pas confondre avec l'interface en tant que type en Java ou C #. Le deuxième principe vient de la surutilisation observée de l'héritage qui est malheureusement encore courante aujourd'hui .

De là, vous pouvez lire sur les principes SOLID qui a été fait connaître par Robert Cecil Martin (alias. Oncle Bob) . Scott Hanselman a interviewé Oncle Bob dans un podcast sur ces principes :

  • S Ingle Principe Responsabilité
  • Principe de fermeture O pen
  • Principe de substitution de L iskov
  • Je NTERFACE Principe Ségrégation
  • D ependency Principe d' inversion

Ces principes sont un bon début pour lire et discuter avec vos pairs. Vous pouvez constater que les principes s'entremêlent les uns avec les autres et avec d'autres processus tels que la séparation des préoccupations et l' injection de dépendances . Après avoir fait du TDD pendant un certain temps, vous pouvez également constater que ces principes viennent naturellement dans la pratique car vous devez les suivre dans une certaine mesure afin de créer des tests unitaires isolés et répétables .


7
+1 Très bonne réponse. Il semble que chaque programmeur (débutant) connaît aujourd'hui ses modèles de conception ou du moins sait qu'ils existent. Mais beaucoup n'ont jamais entendu parler, et encore moins appliquer, certains des principes absolument essentiels comme la responsabilité unique pour gérer la complexité de leur code.
eljenso

21

Celui qui préoccupait le plus les auteurs de Design Patterns était le motif "Visiteur".

C'est un «mal nécessaire» - mais il est souvent surutilisé et le besoin d'en révéler souvent une faille plus fondamentale dans votre conception.

Un autre nom pour le modèle "Visiteur" est "Multi-dispatch", car le modèle Visiteur est ce que vous obtenez lorsque vous souhaitez utiliser un langage OO de répartition de type unique pour sélectionner le code à utiliser en fonction du type de deux (ou plus) différents objets.

L'exemple classique est que vous avez l'intersection entre deux formes, mais il y a un cas encore plus simple qui est souvent négligé: comparer l'égalité de deux objets hétérogènes.

Quoi qu'il en soit, vous vous retrouvez souvent avec quelque chose comme ceci:

interface IShape
{
    double intersectWith(Triangle t);
    double intersectWith(Rectangle r);
    double intersectWith(Circle c);
}

Le problème avec ceci est que vous avez couplé ensemble toutes vos implémentations de "IShape". Vous avez laissé entendre que chaque fois que vous souhaitez ajouter une nouvelle forme à la hiérarchie, vous devrez également modifier toutes les autres implémentations "Shape".

Parfois, c'est la conception minimale correcte - mais réfléchissez-y. Votre conception exige- t-elle vraiment que vous deviez expédier sur deux types? Êtes-vous prêt à écrire chacune de l'explosion combinatoire des multi-méthodes?

Souvent, en introduisant un autre concept, vous pouvez réduire le nombre de combinaisons que vous allez devoir écrire:

interface IShape
{
    Area getArea();
}

class Area
{
    public double intersectWith(Area otherArea);
    ...
}

Bien sûr, cela dépend - parfois vous avez vraiment besoin d'écrire du code pour gérer tous ces différents cas - mais cela vaut la peine de prendre une pause et de réfléchir avant de sauter le pas et d'utiliser Visitor. Cela pourrait vous éviter beaucoup de douleur plus tard.


2
En parlant de visiteurs, Oncle Bob l'utilise "tout le temps" butunclebob.com/ArticleS.UncleBob.IuseVisitor
Spoike

3
@Paul Hollingsworth Pouvez-vous indiquer où il est dit que les auteurs de Design Patterns sont inquiets (et pourquoi sont-ils inquiets)?
m3th0dman

16

Singletons - une classe utilisant singleton X a une dépendance qui est difficile à voir et à isoler pour les tests.

Ils sont utilisés très souvent car ils sont pratiques et faciles à comprendre, mais ils peuvent vraiment compliquer les tests.

Voir Les singletons sont des menteurs pathologiques .


1
Ils peuvent également simplement tester car ils peuvent vous donner un point unique pour injecter un objet Mock. Tout dépend du bon équilibre.
Martin Brown

1
@Martin: S'il est bien sûr possible de changer un singelton pour le test (si vous n'utilisez pas l'implémentation standard de singelton), mais en quoi est-ce plus facile que de passer l'implémentation du test dans le constructeur?
orip le

14

Je pense que le modèle de méthode de modèle est généralement un modèle très dangereux.

  • Souvent, il utilise votre hiérarchie d'héritage pour «les mauvaises raisons».
  • Les classes de base ont tendance à devenir jonchées de toutes sortes de codes non liés.
  • Cela vous oblige à verrouiller la conception, souvent assez tôt dans le processus de développement. (Verrouillage prématuré dans de nombreux cas)
  • Changer cela à un stade ultérieur devient de plus en plus difficile.

2
J'ajouterais que chaque fois que vous utilisez la méthode de modèle, il est probablement préférable d'utiliser la stratégie. Le problème avec TemplateMethod est qu'il existe une réentrance entre la classe de base et la classe dérivée, qui est souvent trop couplée.
Paul Hollingsworth

5
@Paul: La méthode des modèles est excellente lorsqu'elle est utilisée correctement, c'est-à-dire lorsque les parties qui varient ont besoin d'en savoir beaucoup sur les parties qui ne le sont pas. Mon opinion est que la stratégie doit être utilisée lorsque le code de base appelle uniquement le code personnalisé et que la méthode de modèle doit être utilisée lorsque le code personnalisé a intrinsèquement besoin de connaître le code de base.
dsimcha

ouais dsimcha, je suis d'accord ... tant que le concepteur de la classe en est conscient.
Paul Hollingsworth

9

Je ne pense pas que vous devriez éviter les Design Patterns (DP), et je ne pense pas que vous devriez vous forcer à utiliser des DP lors de la planification de votre architecture. Nous ne devrions utiliser les PD que lorsqu'ils émergent naturellement de notre planification.

Si nous définissons dès le départ que nous voulons utiliser un DP donné, nombre de nos futures décisions de conception seront influencées par ce choix, sans aucune garantie que le DP que nous avons choisi est adapté à nos besoins.

Une chose que nous ne devrions pas non plus faire est de traiter un DP comme une entité immuable, nous devons adapter le modèle à nos besoins.

Donc, en résumé, je ne pense pas que nous devrions éviter les PDD, nous devrions les embrasser quand ils prennent déjà forme dans notre architecture.


7

Je pense qu'Active Record est un modèle surutilisé qui encourage à mélanger la logique métier avec le code de persistance. Il ne fait pas un très bon travail de masquer l'implémentation de stockage de la couche de modèle et lie les modèles à une base de données. Il existe de nombreuses alternatives (décrites dans PoEAA) telles que Table Data Gateway, Row Data Gateway et Data Mapper qui fournissent souvent une meilleure solution et contribuent certainement à fournir une meilleure abstraction au stockage. De plus, votre modèle ne devrait pas avoir besoin d'être stocké dans une base de données; qu'en est-il de les stocker au format XML ou d'y accéder à l'aide de services Web? Serait-il facile de changer le mécanisme de stockage de vos modèles?

Cela dit, Active Record n'est pas toujours mauvais et est parfait pour les applications plus simples où les autres options seraient exagérées.


1
Un peu vrai, mais dépend dans une certaine mesure de la mise en œuvre.
Mike Woodhouse

6

C'est simple ... évitez les modèles de conception qui ne sont pas clairs pour vous ou ceux avec lesquels vous ne vous sentez pas à l'aise .

Pour en nommer quelques-uns ...

il y a des modèles peu pratiques , comme par exemple:

  • Interpreter
  • Flyweight

il y a aussi des plus difficiles à saisir , comme par exemple:

  • Abstract Factory - Le motif d'usine abstrait complet avec des familles d'objets créés n'est pas un jeu d'enfant qu'il n'y paraît
  • Bridge - Peut devenir trop abstrait, si l'abstraction et l'implémentation sont divisées en sous-arbres, mais c'est un modèle très utilisable dans certains cas
  • Visitor - La compréhension du double mécanisme de répartition est vraiment un MUST

et il y a des modèles qui semblent terriblement simples , mais qui ne sont pas un choix aussi clair en raison de diverses raisons liées à leur principe ou à leur mise en œuvre:

  • Singleton - motif pas vraiment totalement mauvais, juste trop utilisé (souvent là où il ne convient pas)
  • Observer - excellent modèle ... rend juste le code beaucoup plus difficile à lire et à déboguer
  • Prototype - échange des vérifications du compilateur pour le dynamisme (qui peut être bon ou mauvais ... dépend)
  • Chain of responsibility - trop souvent simplement poussé de force / artificiellement dans la conception

Pour ceux "peu pratiques", il faut vraiment réfléchir avant de les utiliser, car il y a généralement une solution plus élégante quelque part.

Pour les "plus difficiles à saisir" ... ils sont vraiment d'une grande aide, lorsqu'ils sont utilisés à des endroits appropriés et lorsqu'ils sont bien mis en œuvre ... mais ils sont cauchemardesques, lorsqu'ils sont mal utilisés.

Maintenant, quelle est la suite ...


Le modèle Flyweight est indispensable à tout moment lorsque vous utilisez une ressource, souvent une image, plus d'une fois. Ce n'est pas un modèle, c'est une solution.
Cengiz Kandemir

5

J'espère que je ne serai pas trop battu pour ça. Christer Ericsson a écrit deux articles ( un , deux ) sur le thème des modèles de conception dans son blog de détection de collision en temps réel . Son ton est plutôt dur, et peut-être un peu provocateur, mais l'homme connaît son affaire, donc je ne le considérerais pas comme des délires d'un fou.


Lectures intéressantes. Merci pour les liens!
Bombe

3
Les abrutis produisent du mauvais code. Les abrutis avec des modèles produisent-ils un code pire que des abrutis qui n'ont jamais vu de modèles? Je ne pense pas qu'ils le fassent. Pour les personnes intelligentes, les modèles fournissent un vocabulaire bien connu, ce qui facilite l'échange d'idées. La solution: apprenez des modèles et ne traitez qu'avec des programmeurs intelligents.
Martin Brown

Je ne pense pas qu'il soit vraiment possible pour un vrai crétin de produire un code pire - quel que soit l'outil qu'il utilise
1800 INFORMATIONS

1
Je pense que son exemple avec son test universitaire prouve seulement que les personnes qui méprisent leur domaine de problème et ne veulent pas l'étudier pendant plus de quelques heures au cours d'un seul week-end produiront des réponses incorrectes lorsqu'elles tenteront de résoudre des problèmes.
scriptocalypse

5

Certains disent que le localisateur de services est un anti-pattern.


Il est également important de noter que parfois un localisateur de service est nécessaire. Par exemple, lorsque vous ne maîtrisez pas correctement l'instanciation de l'objet (par exemple des attributs avec des paramètres non constants en C #). Mais il est également possible d'utiliser le localisateur de service AVEC injection ctor.
Sinaesthetic

2

Je pense que le modèle d'observateur a beaucoup à répondre, cela fonctionne dans des cas très généraux, mais à mesure que les systèmes deviennent plus complexes, cela devient un cauchemar, nécessitant des notifications OnBefore (), OnAfter () et souvent l'affichage de tâches asynchrones pour éviter de entrée. Une bien meilleure solution consiste à développer un système d'analyse automatique des dépendances qui instrumente tous les accès aux objets (avec des barrières de lecture) pendant les calculs et crée automatiquement une arête dans un graphe de dépendances.


4
J'ai tout compris dans votre réponse jusqu'au mot "A"
1800 INFORMATIONS

Vous devrez peut-être développer ou créer un lien vers cette analyse automatique des dépendances dont vous parlez. Également dans .NET, les délégués / événements sont utilisés à la place du modèle d'observateur.
Spoike

3
@Spoike: les délégués / événements sont une implémentation du modèle d'observateur
orip

1
Ma rancune personnelle contre Observer est qu'il peut créer des fuites de mémoire dans les langues de récupération de place. Lorsque vous avez terminé avec un objet, vous devez vous rappeler que l'objet ne sera pas nettoyé.
Martin Brown

@orip: oui, c'est pourquoi vous utilisez des délégués / événements. ;)
Spoike

2

Un complément à l'article de Spoike, Refactoring to Patterns est une bonne lecture.


J'ai en fait un lien vers le catalogue du livre sur Internet. :)
Spoike

Oh! Je n'ai pas pris la peine de le survoler. En fait, juste après avoir terminé la question, ce livre m'est venu à l'esprit, puis j'ai vu votre réponse. Je ne pouvais pas m'empêcher de le publier. :)
Adeel Ansari

0

Iterator est un autre modèle GoF à éviter, ou du moins à ne l'utiliser que lorsqu'aucune alternative n'est disponible.

Les alternatives sont:

  1. pour chaque boucle. Cette construction est présente dans la plupart des langages traditionnels et peut être utilisée pour éviter les itérateurs dans la majorité des cas.

  2. sélecteurs à la LINQ ou jQuery. Ils doivent être utilisés lorsque for-each n'est pas approprié car tous les objets du conteneur ne doivent pas être traités. Contrairement aux itérateurs, les sélecteurs permettent de manifester en un seul endroit le type d'objets à traiter.


Je suis d'accord avec les sélecteurs. Foreach est un itérateur, la plupart des langages OO fournissent une interface itérative que vous implémentez pour autoriser un foreach.
Neil Aitken

Dans certains langages, chaque construction peut être implémentée par des itérateurs, mais son concept est en fait plus haut niveau et plus proche des sélecteurs. Lors de l'utilisation de for-each, le développeur déclare explicitement que tous les éléments du conteneur doivent être traités.
Volodymyr Frolov

Les itérateurs sont un excellent modèle. L'anti-modèle implémenterait IEnumerable / IEnumerator sans itérateur. Je pense que LINQ a été rendu possible grâce à l' yielditérateur. Eric White a de bonnes discussions à ce sujet en C # 3.0: blogs.msdn.com/b/ericwhite/archive/2006/10/04/… . Consultez également la discussion de Jeremy Likness sur les coroutines avec les itérateurs: wintellect.com/CS/blogs/jlikness/archive/2010/03/23/… .

@Ryan Riley, les itérateurs sont des objets de bas niveau et doivent donc être évités dans la conception et le code de haut niveau. Les détails de l'implémentation des itérateurs et des différents types de sélecteurs n'ont pas d'importance ici. Les sélecteurs, contrairement aux itérateurs, permettent aux programmeurs d'exprimer explicitement ce qu'ils veulent traiter et de sorte qu'ils soient de haut niveau.
Volodymyr Frolov du

Fwiw, la syntaxe F # alternative, semblable à LINQ, est `List.map (fun x -> x.Value) xs`, qui est à peu près aussi longue que la compréhension de la liste.
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.