Entwurfsmuster zur Vermeidung von [geschlossen]

105

Viele Leute scheinen zuzustimmen, dass das Singleton-Muster eine Reihe von Nachteilen aufweist, und einige schlagen sogar vor, das Muster vollständig zu vermeiden. Hier gibt es eine ausgezeichnete Diskussion . Bitte richten Sie alle Kommentare zum Singleton-Muster auf diese Frage.

Meine Frage : Gibt es andere Designmuster, die vermieden oder mit großer Sorgfalt verwendet werden sollten?

Brian Rasmussen
quelle
Ich muss nur über die große Liste von Design Antipatterns deviq.com/antipatterns
Developer
@casperOne warum hast du die Frage geschlossen? Die Frage war legitim.
bleepzter

Antworten:

148

Muster sind komplex

Alle Designmuster sollten mit Vorsicht verwendet werden. Meiner Meinung nach sollten Sie Muster umgestalten, wenn es einen triftigen Grund dafür gibt, anstatt sofort ein Muster zu implementieren. Das allgemeine Problem bei der Verwendung von Mustern besteht darin, dass sie die Komplexität erhöhen. Die übermäßige Verwendung von Mustern erschwert die Weiterentwicklung und Wartung einer bestimmten Anwendung oder eines bestimmten Systems.

Meistens gibt es eine einfache Lösung, und Sie müssen kein bestimmtes Muster anwenden. Eine gute Faustregel ist, ein Muster zu verwenden, wenn Codeteile häufig ersetzt werden oder häufig geändert werden müssen, und bereit zu sein, die Einschränkung des komplexen Codes bei der Verwendung eines Musters zu übernehmen.

Denken Sie daran, dass Ihr Ziel die Einfachheit sein und ein Muster verwenden sollte, wenn Sie eine praktische Notwendigkeit sehen, Änderungen in Ihrem Code zu unterstützen.

Prinzipien über Muster

Es mag umstritten erscheinen, Muster zu verwenden, wenn sie offensichtlich zu überentwickelten und komplexen Lösungen führen können. Stattdessen ist es für einen Programmierer viel interessanter, sich über Entwurfstechniken und -prinzipien zu informieren, die die Grundlage für die meisten Muster bilden. Tatsächlich betont eines meiner Lieblingsbücher über „Entwurfsmuster“ dies, indem es wiederholt, welche Prinzipien auf das betreffende Muster anwendbar sind. Sie sind einfach genug, um in Bezug auf die Relevanz nützlicher zu sein als Muster. Einige der Prinzipien sind allgemein genug, um mehr als objektorientierte Programmierung (OOP) zu umfassen, wie z. B. das Liskov-Substitutionsprinzip , solange Sie Module Ihres Codes erstellen können.

Es gibt eine Vielzahl von Gestaltungsprinzipien, aber die im ersten Kapitel des GoF-Buches beschriebenen sind zunächst sehr nützlich.

  • Programmieren Sie auf eine 'Schnittstelle', nicht auf eine 'Implementierung'. (Gang of Four 1995: 18)
  • Bevorzugen Sie "Objektzusammensetzung" gegenüber "Klassenvererbung". (Gang of Four 1995: 20)

Lass die für eine Weile in dir versinken. Es sollte beachtet werden, dass eine Schnittstelle beim Schreiben von GoF alles bedeutet, was eine Abstraktion ist (was auch Superklassen bedeutet), nicht zu verwechseln mit der Schnittstelle als Typ in Java oder C #. Das zweite Prinzip beruht auf der beobachteten Überbeanspruchung der Vererbung, die heute leider noch weit verbreitet ist .

Von dort aus können Sie sich über SOLID-Prinzipien informieren, die von Robert Cecil Martin (auch bekannt als Onkel Bob) bekannt gemacht wurden . Scott Hanselman interviewte Onkel Bob in einem Podcast über diese Prinzipien :

  • S ingle Prinzip Verantwortung
  • O Stift Geschlossenes Prinzip
  • L iskov Substitutionsprinzip
  • I nterface Segregationsprinzip
  • D ependenzinversionsprinzip

Diese Grundsätze sind ein guter Anfang, um sie zu lesen und mit Ihren Kollegen zu diskutieren. Möglicherweise stellen Sie fest, dass die Prinzipien miteinander und mit anderen Prozessen wie der Trennung von Bedenken und der Abhängigkeitsinjektion verwoben sind . Nachdem Sie eine Weile TDD durchgeführt haben, stellen Sie möglicherweise auch fest, dass diese Prinzipien in der Praxis selbstverständlich sind, da Sie sie bis zu einem gewissen Grad befolgen müssen, um isolierte und wiederholbare Komponententests zu erstellen .

Spoike
quelle
7
+1 Sehr gute Antwort. Es scheint, dass jeder (Anfänger-) Programmierer heute seine Designmuster kennt oder zumindest weiß, dass sie existieren. Aber sehr viele haben noch nie von einigen der absolut wesentlichen Prinzipien wie der Einzelverantwortung für die Verwaltung der Komplexität ihres Codes gehört, geschweige denn angewendet.
Eljenso
21

Das, worüber sich die Autoren von Design Patterns am meisten Sorgen machten, war das "Besuchermuster".

Es ist ein "notwendiges Übel" - wird aber oft überstrapaziert und die Notwendigkeit zeigt oft einen grundlegenderen Fehler in Ihrem Design.

Ein alternativer Name für das Muster "Besucher" ist "Mehrfachversand", da das Besuchermuster genau das ist, was Sie erhalten, wenn Sie eine Versand-OO-Sprache mit einem Typ verwenden möchten, um den zu verwendenden Code basierend auf dem Typ von zwei auszuwählen (oder mehr) verschiedene Objekte.

Das klassische Beispiel ist, dass Sie den Schnittpunkt zwischen zwei Formen haben, aber es gibt einen noch einfacheren Fall, der oft übersehen wird: den Vergleich der Gleichheit zweier heterogener Objekte.

Wie auch immer, oft hat man so etwas:

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

Das Problem dabei ist, dass Sie alle Ihre Implementierungen von "IShape" miteinander gekoppelt haben. Sie haben impliziert, dass Sie, wenn Sie der Hierarchie eine neue Form hinzufügen möchten, auch alle anderen "Form" -Implementierungen ändern müssen.

Manchmal ist dies das richtige Minimal-Design - aber denken Sie darüber nach. Erfordert Ihr Design wirklich , dass Sie zwei Typen versenden müssen? Sind Sie bereit, jede der kombinatorischen Explosionen von Multi-Methoden zu schreiben?

Durch die Einführung eines anderen Konzepts können Sie häufig die Anzahl der Kombinationen reduzieren, die Sie tatsächlich schreiben müssen:

interface IShape
{
    Area getArea();
}

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

Natürlich kommt es darauf an - manchmal muss man wirklich Code schreiben, um all diese verschiedenen Fälle zu behandeln -, aber es lohnt sich, eine Pause einzulegen und nachzudenken, bevor man den Sprung wagt und Visitor verwendet. Es könnte Ihnen später viel Schmerz ersparen.

Paul Hollingsworth
quelle
2
Onkel Bob spricht von Besuchern "ständig", aberunclebob.com/ArticleS.UncleBob.IuseVisitor
Spoike
3
@Paul Hollingsworth Können Sie einen Hinweis darauf geben, wo es heißt, dass die Autoren von Design Patterns besorgt sind (und warum sie besorgt sind)?
m3th0dman
16

Singletons - Eine Klasse, die Singleton X verwendet, hat eine Abhängigkeit davon, die für Tests schwer zu erkennen und zu isolieren ist.

Sie werden sehr oft verwendet, weil sie bequem und leicht zu verstehen sind, aber sie können das Testen wirklich erschweren.

Siehe Singletons sind pathologische Lügner .

orip
quelle
1
Sie können auch einfach testen, da sie Ihnen einen einzigen Punkt zum Injizieren eines Mock-Objekts geben können. Es kommt darauf an, das richtige Gleichgewicht zu finden.
Martin Brown
1
@Martin: Wenn es natürlich möglich ist, ein Singelton zum Testen zu ändern (wenn Sie nicht die Standard-Singelton-Implementierung verwenden), aber wie ist das einfacher, als die Testimplementierung im Konstruktor zu bestehen?
Orip
14

Ich glaube, dass das Muster der Vorlagenmethode im Allgemeinen ein sehr gefährliches Muster ist.

  • Oft verbraucht es Ihre Vererbungshierarchie aus "den falschen Gründen".
  • Basisklassen neigen dazu, mit allen Arten von nicht verwandtem Code übersät zu werden.
  • Es zwingt Sie, das Design zu sperren, oft ziemlich früh im Entwicklungsprozess. (In vielen Fällen vorzeitige Sperre)
  • Dies zu einem späteren Zeitpunkt zu ändern, wird immer schwieriger.
Krosenvold
quelle
2
Ich würde hinzufügen, dass Sie wahrscheinlich immer besser Strategie verwenden, wenn Sie die Vorlagenmethode verwenden. Das Problem bei TemplateMethod besteht darin, dass zwischen der Basisklasse und der abgeleiteten Klasse ein Wiedereintritt besteht, der häufig übermäßig gekoppelt ist.
Paul Hollingsworth
5
@Paul: Die Vorlagenmethode ist großartig, wenn sie richtig verwendet wird, dh wenn die Teile, die variieren, viel über die Teile wissen müssen, die dies nicht tun. Meiner Meinung nach sollte die Strategie verwendet werden, wenn der Basiscode nur den benutzerdefinierten Code aufruft, und die Vorlagenmethode sollte verwendet werden, wenn der benutzerdefinierte Code von Natur aus über den Basiscode Bescheid wissen muss.
Dsimcha
Ja, Dsimcha, ich stimme zu ... solange der Klassendesigner sich dessen bewusst ist.
Paul Hollingsworth
9

Ich denke nicht, dass Sie Design Patterns (DP) vermeiden sollten, und ich denke nicht, dass Sie sich zwingen sollten, DPs bei der Planung Ihrer Architektur zu verwenden. Wir sollten DPs nur verwenden, wenn sie natürlich aus unserer Planung hervorgehen.

Wenn wir von Anfang an definieren, dass wir einen bestimmten DP verwenden möchten, werden viele unserer zukünftigen Entwurfsentscheidungen von dieser Wahl beeinflusst, ohne dass garantiert wird, dass der von uns ausgewählte DP für unsere Anforderungen geeignet ist.

Eine Sache, die wir auch nicht tun sollten, ist, einen DP als unveränderliche Einheit zu behandeln, wir sollten das Muster an unsere Bedürfnisse anpassen.

Zusammenfassend denke ich also nicht, dass wir DPs vermeiden sollten, wir sollten sie annehmen, wenn sie in unserer Architektur bereits Gestalt annehmen.

Megacan
quelle
7

Ich denke, dass Active Record ein überstrapaziertes Muster ist, das das Mischen von Geschäftslogik mit dem Persistenzcode fördert. Es ist nicht sehr gut, die Speicherimplementierung vor der Modellebene zu verbergen und die Modelle an eine Datenbank zu binden. Es gibt viele Alternativen (in PoEAA beschrieben) wie Table Data Gateway, Row Data Gateway und Data Mapper, die häufig eine bessere Lösung bieten und sicherlich dazu beitragen, den Speicher besser zu abstrahieren. Außerdem sollte Ihr Modell nicht braucht in einer Datenbank gespeichert werden; Was ist mit dem Speichern als XML oder dem Zugriff über Webdienste? Wie einfach wäre es, den Speichermechanismus Ihrer Modelle zu ändern?

Trotzdem ist Active Record nicht immer schlecht und eignet sich perfekt für einfachere Anwendungen, bei denen die anderen Optionen übertrieben wären.

Tim Wardle
quelle
1
Ein bisschen wahr, aber in gewissem Maße abhängig von der Implementierung.
Mike Woodhouse
6

Es ist ganz einfach ... Vermeiden Sie Designmuster, die Ihnen nicht klar sind oder in denen Sie sich nicht wohl fühlen .

Um nur einige zu nennen ...

Es gibt einige unpraktische Muster , wie z.

  • Interpreter
  • Flyweight

es gibt auch einige schwerer zu erfassen , wie zB:

  • Abstract Factory - Ein vollständiges abstraktes Fabrikmuster mit Familien erstellter Objekte ist kein Kinderspiel, wie es scheint
  • Bridge - Kann zu abstrakt werden, wenn Abstraktion und Implementierung in Teilbäume unterteilt sind, ist aber in einigen Fällen ein sehr brauchbares Muster
  • Visitor - Das Verständnis des doppelten Versandmechanismus ist wirklich ein MUSS

und es gibt einige Muster, die furchtbar einfach aussehen , aber aus verschiedenen Gründen, die mit ihrem Prinzip oder ihrer Umsetzung zusammenhängen , keine so klare Wahl sind :

  • Singleton - nicht wirklich total schlechtes Muster, nur zu überstrapaziert (oft dort, wo es nicht geeignet ist)
  • Observer - Tolles Muster ... macht das Lesen und Debuggen von Code nur viel schwieriger
  • Prototype - tauscht Compiler-Checks auf Dynamik aus (was gut oder schlecht sein kann ... hängt davon ab)
  • Chain of responsibility - zu oft nur zwangsweise / künstlich in das Design hineingedrückt

Für diese "unpraktischen" sollte man wirklich überlegen, bevor man sie verwendet, da es normalerweise irgendwo eine elegantere Lösung gibt.

Für die "schwerer zu fassenden" ... sind sie wirklich eine große Hilfe, wenn sie an geeigneten Stellen verwendet werden und wenn sie gut implementiert sind ... aber sie sind ein Albtraum, wenn sie unsachgemäß verwendet werden.

Nun, was kommt als nächstes ...

Marcel Toth
quelle
Das Fliegengewichtsmuster ist immer dann ein Muss, wenn Sie eine Ressource, häufig ein Bild, mehrmals verwenden. Es ist kein Muster, es ist eine Lösung.
Cengiz Kandemir
5

Ich hoffe, ich werde dafür nicht zu sehr geschlagen. Christer Ericsson hat in seinem Echtzeit-Blog zur Kollisionserkennung zwei Artikel ( eins , zwei ) zum Thema Entwurfsmuster geschrieben . Sein Ton ist ziemlich hart und vielleicht ein bisschen provokativ, aber der Mann kennt sich aus, also würde ich es nicht als Schwärmerei eines Verrückten abtun.

falstro
quelle
Interessante liest. Danke für die Links!
Bombe
3
Idioten produzieren schlechten Code. Produzieren Idioten mit Mustern schlechteren Code als Idioten, die noch nie Muster gesehen haben? Ich glaube nicht, dass sie es tun. Für kluge Leute bieten Muster ein bekanntes Vokabular, das den Gedankenaustausch erleichtert. Die Lösung: Lernen Sie Muster und beschäftigen Sie sich nur mit intelligenten Programmierern.
Martin Brown
Ich denke nicht, dass es wirklich möglich ist, dass ein echter Idiot schlechteren Code produziert - egal welches Tool er verwendet
1800 INFORMATION
1
Ich denke, sein Beispiel mit seinem College-Test beweist nur, dass Menschen, die ihre Problemdomäne verachten und nicht bereit sind, sie an einem einzigen Wochenende länger als ein paar Stunden zu studieren, falsche Antworten liefern, wenn sie versuchen, Probleme zu lösen.
Scriptocalypse
5

Einige sagen, dass der Service Locator ein Anti-Pattern ist.

Arnis Lapsa
quelle
Es ist auch wichtig zu beachten, dass manchmal ein Service Locator erforderlich ist. Zum Beispiel, wenn Sie die Instanziierung des Objekts nicht richtig steuern können (z. B. Attribute mit nicht konstanten Parametern in C #). Es ist aber auch möglich, den Service Locator MIT CTOR-Injektion zu verwenden.
Sinaesthetic
2

Ich glaube, das Beobachtermuster hat eine Menge zu verantworten, es funktioniert in sehr allgemeinen Fällen, aber wenn Systeme komplexer werden, wird es zu einem Albtraum, der OnBefore () -, OnAfter () - Benachrichtigungen und häufig das Posten asynchroner Aufgaben erfordert, um eine erneute Verwendung zu vermeiden Eingang. Eine viel bessere Lösung besteht darin, ein System zur automatischen Abhängigkeitsanalyse zu entwickeln, das alle Objektzugriffe (mit Lesebarrieren) während der Berechnungen instrumentiert und automatisch eine Kante in einem Abhängigkeitsdiagramm erstellt.

Jesse Pepper
quelle
4
Ich habe alles in Ihrer Antwort bis zum Wort "A" verstanden
1800 INFORMATION
Möglicherweise müssen Sie diese automatische Abhängigkeitsanalyse, über die Sie sprechen, erweitern oder mit ihr verknüpfen. Auch in .NET werden anstelle des Beobachtermusters Delegaten / Ereignisse verwendet.
Spoike
3
@Spoike: Delegierte / Ereignisse sind eine Implementierung des Beobachtermusters
orip
1
Mein persönlicher Groll gegen Observer ist, dass es zu Speicherlecks in durch Müll gesammelten Sprachen kommen kann. Wenn Sie mit einem Objekt fertig sind, müssen Sie sich daran erinnern, dass das Objekt nicht bereinigt wird.
Martin Brown
@orip: Ja, deshalb verwenden Sie Delegaten / Ereignisse. ;)
Spoike
2

Eine Ergänzung zu Spoikes Beitrag " Refactoring to Patterns" ist eine gute Lektüre.

Adeel Ansari
quelle
Ich habe tatsächlich auf den Katalog des Buches im Internet verlinkt. :)
Spoike
Oh! Ich habe mich nicht darum gekümmert, darüber zu schweben. Gleich nach Abschluss der Frage kam mir dieses Buch in den Sinn, und dann sah ich Ihre Antwort. Ich konnte mich nicht aufhalten, es zu posten. :)
Adeel Ansari
0

Iterator ist ein weiteres GoF-Muster, das vermieden oder zumindest nur verwendet werden sollte, wenn keine der Alternativen verfügbar ist.

Alternativen sind:

  1. für jede Schleife. Diese Konstruktion ist in den meisten gängigen Sprachen vorhanden und kann in den meisten Fällen verwendet werden, um Iteratoren zu vermeiden.

  2. Selektoren à la LINQ oder jQuery. Sie sollten verwendet werden, wenn for-each nicht geeignet ist, da nicht alle Objekte aus dem Container verarbeitet werden sollten. Im Gegensatz zu Iteratoren können Selektoren an einer Stelle angeben, welche Art von Objekten verarbeitet werden sollen.

Volodymyr Frolov
quelle
Ich stimme den Selektoren zu. Foreach ist ein Iterator. Die meisten OO-Sprachen bieten eine iterierbare Schnittstelle, die Sie implementieren, um einen foreach zu ermöglichen.
Neil Aitken
In einigen Sprachen kann jede Konstruktion durch Iteratoren implementiert werden, aber das Konzept ist tatsächlich auf höherer Ebene und näher an den Selektoren. Bei Verwendung von for-each erklärt der Entwickler ausdrücklich, dass alle Elemente aus dem Container verarbeitet werden sollen.
Volodymyr Frolov
Iteratoren sind ein großartiges Muster. Das Anti-Pattern würde IEnumerable / IEnumerator ohne Iterator implementieren . Ich glaube, LINQ wurde durch den yieldIterator ermöglicht. Eric White hat in C # 3.0 eine großartige Diskussion darüber: blogs.msdn.com/b/ericwhite/archive/2006/10/04/… . Lesen Sie auch die Diskussion von Jeremy Likness über Coroutinen mit Iteratoren: wintellect.com/CS/blogs/jlikness/archive/2010/03/23/… .
@ Ryan Riley, Iteratoren sind Objekte auf niedriger Ebene und sollten daher in Design und Code auf hoher Ebene vermieden werden. Details zur Implementierung von Iteratoren und verschiedenen Arten von Selektoren spielen hier keine Rolle. Mit Selektoren können Programmierer im Gegensatz zu Iteratoren explizit ausdrücken, was sie verarbeiten möchten, und dies auf hohem Niveau.
Volodymyr Frolov
Fwiw, die alternative, LINQ-ähnliche F # -Syntax, ist `List.map (fun x -> x.Value) xs`, was ungefähr so ​​lang ist wie das Listenverständnis.