Ich würde es wahrscheinlich als Code-Geruch oder sogar als Anti-Pattern bezeichnen, eine Klasse zu haben, die 23 Interfaces implementiert. Wenn es sich in der Tat um ein Anti-Pattern handelt, wie würden Sie es nennen? Oder folgt es einfach nicht dem Grundsatz der einheitlichen Verantwortung?
object-oriented
anti-patterns
solid
Jonas Elfström
quelle
quelle
Antworten:
Royal Family: Sie machen nichts besonders Verrücktes, aber sie haben eine Milliarde Titel und sind irgendwie mit den meisten anderen Royals verwandt.
quelle
somehow
Ich reiche ein, dass dieses Anti-Muster Jack of All Trades oder vielleicht Too Many Hats genannt wird.
quelle
Wenn ich ihm einen Namen geben müsste, würde ich ihn die Hydra nennen :
Von besonderer Bedeutung ist die Tatsache, dass es nicht nur viele Köpfe hat, sondern immer mehr von ihnen wächst und deshalb unmöglich zu töten ist. Das war meine Erfahrung mit solchen Designs. Die Entwickler mischen immer mehr Benutzeroberflächen ein, bis zu viele auf den Bildschirm passen, und bis dahin ist das Programm so tief im Design und in den Annahmen verankert, dass die Aufteilung des Programms eine hoffnungslose Perspektive ist (wenn Sie es versuchen, ist es tatsächlich so) Oft werden mehr Schnittstellen benötigt, um die Lücke zu schließen.
Ein frühes Anzeichen für ein bevorstehendes Schicksal aufgrund einer "Hydra" ist das häufige Werfen einer Schnittstelle zu einer anderen, ohne viel Vernunftsprüfung, und oft zu einem Ziel, das keinen Sinn ergibt, wie in:
Es ist offensichtlich, dass dieser Code aus dem Zusammenhang gerissen ist, aber die Wahrscheinlichkeit dafür steigt, je mehr "Köpfe" ein Objekt hat, da Entwickler intuitiv wissen, dass sie immer mit derselben Kreatur zu tun haben.
Viel Glück jemals tun Wartungsarbeiten an einem Objekt , dass Geräte 23 Schnittstellen. Die Wahrscheinlichkeit, dass Sie dabei keinen Kollateralschaden verursachen, ist gering .
quelle
Das Gott-Objekt kommt in den Sinn; Ein einzelnes Objekt, das ALLES kann. Dies liegt an der geringen Einhaltung der "Kohäsions" -Anforderungen der beiden wichtigsten Entwurfsmethoden. Wenn Sie ein Objekt mit 23 Schnittstellen haben, haben Sie ein Objekt, das weiß, wie 23 verschiedene Dinge für seine Benutzer zu sein haben, und diese 23 verschiedenen Dinge sind wahrscheinlich nicht im Sinne einer einzelnen Aufgabe oder eines einzelnen Bereichs des Systems.
In SOLID hat der Ersteller dieses Objekts offensichtlich versucht, das Prinzip der Schnittstellentrennung zu befolgen, aber gegen eine vorherige Regel verstoßen. Grundsatz der einheitlichen Verantwortung. Es gibt einen Grund, warum es "SOLID" ist. S steht immer an erster Stelle, wenn man an Design denkt, und alle anderen Regeln folgen.
In GRASP hat der Schöpfer einer solchen Klasse die "High Cohesion" -Regel vernachlässigt. Im Gegensatz zu SOLID lehrt GRASP, dass ein Objekt keine einzige Verantwortung haben muss, aber höchstens zwei oder drei sehr eng miteinander verbundene Verantwortlichkeiten haben sollte.
quelle
23 ist nur eine Zahl! Im wahrscheinlichsten Fall ist es hoch genug, um Alarm auszulösen. Wenn wir jedoch fragen, wie viele Methoden / Schnittstellen sind am häufigsten vorhanden, bevor ein Tag als "Anti-Pattern" bezeichnet werden kann? Ist es 5 oder 10 oder 25? Sie erkennen, dass diese Zahl wirklich keine Antwort ist, denn wenn 10 gut ist, kann 11 auch gut sein - und danach eine beliebige ganze Zahl.
Die eigentliche Frage betrifft die Komplexität. Und wir sollten darauf hinweisen, dass langwieriger Code, die Anzahl der Methoden oder die Größe der Klasse durch irgendwelche Maßnahmen tatsächlich NICHT die Definition von Komplexität ist. Ja, ein immer größerer Code (größere Anzahl von Methoden) erschwert das Lesen und Begreifen für einen neuen Anfänger. Darüber hinaus werden potenziell unterschiedliche Funktionen, eine große Anzahl von Ausnahmen und ausgereifte Algorithmen für verschiedene Szenarien behandelt. Dies bedeutet nicht, dass es komplex ist - es ist nur schwer zu verdauen.
Andererseits kann ein relativ kleiner Code, von dem man hoffen kann, dass er in ein paar Stunden ein- und ausgelesen wird, immer noch komplex sein. Hier ist, wenn ich denke, dass der Code (unnötig) komplex ist.
Jede Weisheit des objektorientierten Designs kann hier eingesetzt werden, um "komplex" zu definieren, aber ich würde hier einschränken, um zu zeigen, wann "so viele Methoden" ein Hinweis auf Komplexität sind.
Gegenseitiges Wissen. (auch als Kopplung bekannt) Wenn Dinge oft als Klassen geschrieben werden, denken wir alle, dass es sich um "netten" objektorientierten Code handelt. Aber die Annahme über die andere Klasse unterbricht im Wesentlichen die tatsächlich benötigte Verkapselung. Wenn Sie Methoden haben, die tiefe Details über den internen Status der Algorithmen "verraten" - und die Anwendung mit einer Schlüsselannahme über den internen Status der Serving-Klasse erstellt wird.
Zu viele Wiederholungen (Einbruch) Wenn Methoden, die ähnliche Namen haben, aber widersprüchliche Arbeit leisten - oder Namen mit ähnlicher Funktionalität widersprechen. Oftmals entwickeln sich Codes, um leicht unterschiedliche Schnittstellen für verschiedene Anwendungen zu unterstützen.
Zu viele Rollen Wenn die Klasse immer wieder Nebenfunktionen hinzufügt und immer weiter expandiert, nur um zu wissen, dass es sich bei der Klasse tatsächlich um eine Zwei-Klassen-Klasse handelt. Überraschenderweise beginnen alle mit echtAnforderungen und keine andere Klasse existieren, um dies zu tun. Beachten Sie, dass es eine Klasse Transaction gibt, die Details zur Transaktion enthält. Sieht soweit gut aus, jetzt benötigt jemand eine Formatkonvertierung zum "Zeitpunkt der Transaktion" (zwischen UTC und ähnlichem), später fügen Leute Regeln hinzu, um zu überprüfen, ob bestimmte Dinge an bestimmten Daten waren, um ungültige Transaktionen zu validieren. - Ich schreibe nicht die ganze Geschichte, aber irgendwann baut die Transaktionsklasse den gesamten Kalender auf und dann verwenden die Leute "nur den Kalender" als Teil davon! Dies ist sehr komplex (um es sich vorzustellen), warum ich "Transaktionsklasse" instanziiere, um die Funktionalität zu haben, die ein "Kalender" mir bieten würde!
(In) Konsistenz der API Wenn ich book_a_ticket () mache - buche ich ein Ticket! Das ist ganz einfach, unabhängig von der Anzahl der Überprüfungen und Prozesse, die erforderlich sind, um dies zu erreichen. Jetzt wird es komplex, wenn der Zeitfluss dies bewirkt. Normalerweise würde man "suchen" und den möglichen Status "Verfügbar / Nicht verfügbar" zulassen. Um das Hin- und Herbewegen zu verringern, beginnen Sie, einige Kontextzeiger im Ticket zu speichern, und verweisen dann darauf , das Ticket zu buchen. Suche ist nicht die einzige Funktionalität - nach vielen solchen "Nebenfunktionen" wird es schlimmer. Die Bedeutung von book_a_ticket () impliziert dabei book_that_ticket ()! und das kann unvorstellbar komplex sein.
Wahrscheinlich gibt es viele solcher Situationen, die Sie in einem sehr weiterentwickelten Code sehen würden, und ich bin sicher, dass viele Leute Szenarien hinzufügen können, in denen "so viele Methoden" einfach keinen Sinn ergeben oder nicht das tun, was Sie offensichtlich denken. Dies ist das Anti-Muster.
Meine persönliche Erfahrung ist , dass , wenn Projekte , die beginnen einigermaßen von unten nach oben, viele Dinge , für die es auf ihrem eigenen -remains begraben oder schlechter echte Klassen gewesen sein sollten bleibt Spaltung zwischen verschiedenen Klassen und Kopplung zunimmt. Am häufigsten, was 10 Klassen verdient hätte, aber nur 4 hat, ist es wahrscheinlich, dass viele von ihnen verwirrende Mehrzweckmethoden und eine große Anzahl von Methoden haben. Nenne es GOTT und DRACHE, das ist SCHLECHT.
ABER Sie stoßen auf SEHR GROSSE Klassen, die ordentlich konsistent sind, 30 Methoden haben - und immer noch sehr SAUBER sind. Sie können gut sein.
quelle
Klassen sollten genau die richtige Anzahl von Schnittstellen haben. nicht mehr und nicht weniger.
"Zu viele" zu sagen, wäre unmöglich, ohne zu prüfen, ob all diese Schnittstellen in dieser Klasse einen nützlichen Zweck erfüllen. Wenn Sie deklarieren, dass ein Objekt eine Schnittstelle implementiert, müssen Sie seine Methoden implementieren, wenn Sie erwarten, dass die Klasse kompiliert wird. (Ich gehe davon aus, dass die Klasse, die Sie betrachten, dies tut.) Wenn alles in der Schnittstelle implementiert ist und diese Implementierungen etwas relativ zu den Innereien der Klasse tun, ist es schwer zu sagen, dass die Implementierung nicht vorhanden sein sollte. Der einzige Fall, den ich mir vorstellen kann, wo eine Schnittstelle, die diese Kriterien erfüllt, nicht hingehört, ist extern: Wenn niemand sie verwendet.
In einigen Sprachen können Interfaces mithilfe eines Mechanismus wie Javas
extends
Schlüsselwort "untergeordnet" werden , und wer auch immer das geschrieben hat, hat es möglicherweise nicht gewusst. Es könnte auch sein, dass alle 23 weit genug entfernt sind, dass eine Aggregation keinen Sinn ergibt.quelle
Es hört sich so an, als hätten sie ein "Getter" / "Setter" -Paar für jede Eigenschaft, wie es für eine typische "Bean" üblich ist, und haben all diese Methoden zur Schnittstelle befördert. Wie wäre es also mit einem " Has Bean ". Oder die mehr Rabelaisian "Flatulence" nach den bekannten Auswirkungen von zu vielen Bohnen.
quelle
Die Anzahl der Schnittstellen wächst häufig, wenn ein generisches Objekt in verschiedenen Umgebungen verwendet wird. In C # ermöglichen die Varianten von IComparable, IEqualityComparer und IComparer das Sortieren in unterschiedlichen Konfigurationen, sodass Sie möglicherweise alle von ihnen implementieren, einige davon möglicherweise mehrmals, da Sie sowohl die generischen stark typisierten Versionen als auch die nicht generischen Versionen implementieren können Außerdem können Sie mehr als eines der Generika implementieren.
Nehmen wir ein Beispielszenario, sagen wir einen Webshop, in dem Sie Credits kaufen können, mit denen Sie etwas anderes kaufen können (auf Stock-Foto-Sites wird dieses Schema häufig verwendet). Möglicherweise haben Sie eine Klasse "Valuta" und eine Klasse "Credits", die dieselbe Basis erben. Valuta verfügt über einige raffinierte Operatorenüberladungen und Vergleichsroutinen, mit denen Sie Berechnungen durchführen können, ohne sich um die Eigenschaft "Currency" sorgen zu müssen (z. B. Hinzufügen von Pfund zu Dollar). Credits sind viel einfacher, haben aber ein anderes Verhalten. Wenn Sie diese miteinander vergleichen möchten, könnten Sie IComparable sowie IComparable und die anderen Varianten von Vergleichsschnittstellen auf beiden implementieren (auch wenn sie eine gemeinsame Implementierung verwenden, sei es in der Basisklasse oder anderswo).
Bei der Implementierung der Serialisierung werden ISerializable und IDeserializationCallback implementiert. Anschließend Undo-Redo-Stacks implementieren: IClonable wird hinzugefügt. IsDirty-Funktionalität: IObservable, INotifyPropertyChanged. Zulassen, dass Benutzer die Werte mithilfe von Zeichenfolgen bearbeiten können: IConvertable ... Die Liste kann fortgesetzt werden ...
In modernen Sprachen sehen wir einen anderen Trend, der hilft, diese Aspekte zu trennen und sie in ihre eigenen Klassen außerhalb der Kernklasse einzuteilen. Die äußere Klasse oder der äußere Aspekt wird dann mit Hilfe von Annotationen (Attributen) der Zielklasse zugeordnet. Oft ist es möglich, die äußeren Aspektklassen mehr oder weniger allgemein zu gestalten.
Die Verwendung von Attributen (Annotation) ist reflexionspflichtig. Ein Nachteil ist der geringfügige (anfängliche) Leistungsverlust. Ein (oft emotionaler) Nachteil ist, dass Prinzipien wie die Verkapselung gelockert werden müssen.
Es gibt immer andere Lösungen, aber für jede raffinierte Lösung gibt es einen Kompromiss oder einen Haken. Wenn Sie beispielsweise eine ORM-Lösung verwenden, müssen möglicherweise alle Eigenschaften als virtuell deklariert werden. Serialisierungslösungen erfordern möglicherweise Standardkonstruktoren für Ihre Klassen. Wenn Sie die Abhängigkeitsinjektion verwenden, werden möglicherweise 23 Schnittstellen in einer einzelnen Klasse implementiert.
In meinen Augen müssen 23 Schnittstellen per Definition nicht schlecht sein. Möglicherweise steckt dahinter ein gut durchdachtes Schema oder eine grundsätzliche Bestimmung wie die Vermeidung von Reflexionen oder extremen Einkapselungsüberzeugungen.
Wann immer Sie einen Job wechseln oder auf einer bestehenden Architektur aufbauen müssen. Mein Rat ist, sich erst einmal voll und ganz vertraut zu machen, nicht alles zu schnell umzugestalten. Hören Sie dem ursprünglichen Entwickler zu (falls er noch da ist) und versuchen Sie, die Gedanken und Ideen hinter dem, was Sie sehen, herauszufinden. Wenn Sie Fragen stellen, tun Sie dies nicht, um sie zu zerstören, sondern um zu lernen ... Ja, jeder hat seine eigenen goldenen Hämmer, aber je mehr Hämmer Sie sammeln können, desto leichter werden Sie mit Kollegen zurechtkommen.
quelle
"Zu viele" ist subjektiv: Programmierstil? Performance? normenkonform? Präzedenzfälle? einfach nur ein Gefühl von Komfort / Vertrauen?
Solange sich Ihr Code richtig verhält und keine Wartbarkeitsprobleme aufwirft, könnte 23 sogar die neue Norm sein. Eines Tages könnte ich dann sagen: "Mit 23 Schnittstellen kann man eine hervorragende Arbeit leisten, siehe: Jonas Elfström".
quelle
Ich würde sagen, dass das Limit eine kleinere Zahl als 23 sein sollte - eher wie 5 oder 7,
dies schließt jedoch keine Anzahl von Schnittstellen ein, die diese Schnittstellen erben oder keine Anzahl von Schnittstellen, die von Basisklassen implementiert werden.
(Insgesamt also N + eine beliebige Anzahl von vererbten Schnittstellen, wobei N <= 7 ist.)
Wenn Ihre Klasse zu viele Schnittstellen implementiert, handelt es sich wahrscheinlich um eine Gottklasse .
quelle