Wie viele sind zu viele Schnittstellen in einer Klasse? [geschlossen]


28

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?


3
Nun, es ist so oder so fraglich. Wenn die Klasse 23 Methoden hat, die alle einer einzigen Verantwortung dienen (nicht alle Klassen benötigen so viele Methoden, aber es ist nicht unmöglich - besonders wenn die meisten drei Zeilen Convenience-Wrapper um andere Methoden sind), haben Sie einfach zu fein abgestimmte Schnittstellen. )

Die betreffende Klasse ist eine Art Entität, und die Schnittstellen beziehen sich hauptsächlich auf Eigenschaften. Es gibt 113 dieser und nur vier Methoden.
Jonas Elfström

6
Ich dachte, dass Interfaces im Wesentlichen für die Eingabe in statisch kompilierten Sprachen verwendet werden. Was ist das Problem? :)
ashes999

2
Nur eine Frage: Sind alle 113 Eigenschaften für jede Instanz dieser Entität ausgefüllt? Oder handelt es sich um eine Art "gemeinsame Entität", die in verschiedenen Kontexten verwendet wird und nur einige Eigenschaften enthält?
David

1
Kehren wir zum konzeptuellen Verständnis dessen zurück, was eine "Klasse" ausmacht. Eine Klasse ist eine einzelne Sache mit einer klar definierten Verantwortung und Rolle. Können Sie Ihr 23-Schnittstellen-Objekt auf diese Weise definieren? Können Sie einen einzelnen Satz (nicht Joycean) posten, der Ihre Klasse angemessen zusammenfasst? Wenn ja, dann sind 23 Schnittstellen in Ordnung. Wenn nicht, dann ist es zu groß, zu kompliziert.
Stephen Gross

Antworten:


36

Royal Family: Sie machen nichts besonders Verrücktes, aber sie haben eine Milliarde Titel und sind irgendwie mit den meisten anderen Royals verwandt.


:-))))))))))))
Emilio Garavaglia

Ich würde betonensomehow
Wolf

23

Ich reiche ein, dass dieses Anti-Muster Jack of All Trades oder vielleicht Too Many Hats genannt wird.


2
Wie wäre es mit dem Schweizer Taschenmesser?
FrustratedWithFormsDesigner

Es sieht so aus, als ob dieser Begriff bereits für Schnittstellen mit zu vielen Methoden verwendet wird :-) Das wäre aber auch hier sinnvoll.
Jalayn

1
@FrustratedWithFormsDesigner Ein Schweizer Taschenmesser würde niemals den schlechten Geschmack haben, eine Schnittstelle mit dem Namen IValue zu haben, die 30 Eigenschaften enthält, denen 20 das neue Modifikator-Schlüsselwort vorangestellt ist.
Jonas Elfström

3
+1 für zu viele Hüte ... um mehrköpfige Gottheiten
Newtopian

1
Ein Schweizer Taschenmesser ist ein Modell der Wiederverwendung. dh ein einzelnes "Blade" kann möglicherweise Methoden ausführen, also ist es wahrscheinlich eine schlechte Analogie!
James Anderson

17

Wenn ich ihm einen Namen geben müsste, würde ich ihn die Hydra nennen :

Hydra

In der griechischen Mythologie war die Lernaean Hydra (Griechisch: Λερναία Ὕδρα) ein uraltes, namenloses, schlangenartiges, chthonisches Wassertier mit Reptilienmerkmalen (wie der Name vermuten lässt), das viele Köpfe besaß - die Dichter erwähnen mehr Köpfe als die Vasenmaler malen, und für jeden abgeschnittenen Kopf wuchsen zwei mehr - und giftiger Atem, so virulent, dass sogar ihre Spuren tödlich waren.

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:

public void CreateWidget(IPartLocator locator, int widgetTypeId)
{
    var partsNeeded = locator.GetPartsForWidget(widgetTypeId);
    return ((IAssembler)locator).BuildWidget(partsNeeded);
}

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 .


16

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.


Ich bin mir nicht sicher, ob dies das Gegenmuster zum Gott-Objekt ist, da Gott nicht durch irgendwelche Schnittstellen eingeschränkt ist. Durch so viele Schnittstellen eingeschränkt zu werden, könnte die Allmacht Gottes verringern. ;) Vielleicht ist das ein Chimera Objekt?
FrustratedWithFormsDesigner

Gott hat viele Gesichter und kann vielen Menschen viele Dinge sein. Wenn die Funktionalität der kombinierten Schnittstellen das Objekt "allwissend" macht, ist es nicht wirklich einschränkend für Gott, oder?
KeithS

1
Es sei denn, das, was die Schnittstellen erstellt hat, ändert sie. Dann muss Gott möglicherweise neu implementiert werden, um sich an Schnittstellenänderungen anzupassen.
FrustratedWithFormsDesigner

3
Es tut weh, dass du denkst, dass ich die Klasse geschaffen habe.
Jonas Elfström

Die Verwendung von "Sie" ist mehr der Plural, der sich auf Ihr Entwicklerteam bezieht. JEMAND in diesem Team, früher oder heute, hat diese Klasse erstellt. Ich werde es jedoch bearbeiten.
KeithS

8

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.

  1. 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.

  2. 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.

  3. 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!

  4. (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.


Die fragliche Klasse verfügt über 23 Schnittstellen, die insgesamt 113 öffentliche Eigenschaften und vier Methoden enthalten. Nur einige von ihnen sind schreibgeschützt. C # verfügt über automatisch implementierte Eigenschaften. Aus diesem Grund unterscheide ich zwischen Methoden und Eigenschaften. Msdn.microsoft.com/en-us/library/bb384054.aspx
Jonas Elfström 10.11.11

7

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 extendsSchlü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.


Ich fand diese Frage ziemlich sprachunabhängig. Der eigentliche Code ist in C #.
Jonas Elfström

Ich habe kein C # durchgeführt, aber es scheint eine ähnliche Form der Schnittstellenvererbung zu unterstützen.
Blrfl

Ja, eine Schnittstelle kann andere Schnittstellen "erben" und so weiter.
Jonas Elfström

Ich finde es interessant, dass sich viele Diskussionen darauf konzentrieren, was Klassen tun sollen und nicht welche Instanzen . Ein Roboter mit einer Vielzahl von Bild- und Audiosensoren, die an einem gemeinsamen Chassis angebracht sind, sollte als eine Einheit modelliert werden, die über einen Standort und eine Ausrichtung verfügt und die Bilder und Geräusche sehen, hören und zuordnen kann. Die gesamte echte Logik hinter diesen Funktionen befindet sich möglicherweise in anderen Klassen. Der Roboter selbst sollte jedoch Methoden wie "Sehen, ob Objekte voraus sind", "Auf Geräusche im Bereich achten" oder "Sehen, ob Objekte voraus aussehen" unterstützen Erzeugen der erkannten Geräusche. "
Supercat

Es kann durchaus sein, dass ein bestimmter Roboter seine Funktionalität auf eine bestimmte Art und Weise in Subsysteme unterteilen sollte, aber in dem Maße, in dem praktischer Code, der den Roboter verwendet, nicht wissen oder sich darum kümmern muss, wie die Funktionalität innerhalb eines bestimmten Roboters unterteilt ist. Wenn die "Augen" und "Ohren" der Außenwelt als separate Objekte ausgesetzt wären, die entsprechenden Sensoren sich jedoch an einem gemeinsamen Ausleger befänden, könnte der externe Code die "Ohren" auffordern, gleichzeitig mit dem "Ohr" auf Geräusche in Bodennähe zu achten es forderte die "Augen" auf, über ein Hindernis zu schauen.
Supercat

1

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.


Es befindet sich in C # und verfügt über automatisch implementierte Eigenschaften. Diese 23 Schnittstellen enthalten insgesamt 113 solcher Eigenschaften.
Jonas Elfström

1

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.


0

"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".


0

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 .

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.