Teilen Sie große Schnittstellen auf

9

Ich verwende eine große Schnittstelle mit ungefähr 50 Methoden, um auf eine Datenbank zuzugreifen. Die Schnittstelle wurde von einem Kollegen von mir geschrieben. Wir haben das besprochen:

Ich: 50 Methoden sind zu viel. Es ist ein Code-Geruch.
Kollege: Was soll ich dagegen tun? Sie möchten den DB-Zugriff - Sie haben ihn.
Ich: Ja, aber es ist unklar und in Zukunft kaum zu warten.
Kollege: OK, Sie haben Recht, es ist nicht schön. Wie soll die Schnittstelle dann aussehen?
Ich: Wie wäre es mit 5 Methoden, die Objekte zurückgeben, die jeweils 10 Methoden haben?

Mmmh, aber wäre das nicht dasselbe? Führt dies wirklich zu mehr Klarheit? Lohnt sich die Mühe?

Hin und wieder bin ich in einer Situation, in der ich eine Schnittstelle möchte und das erste, was mir in den Sinn kommt, ist eine große Schnittstelle. Gibt es dafür ein allgemeines Entwurfsmuster?


Update (als Antwort auf SJuans Kommentar):

Die "Art von Methoden": Es ist eine Schnittstelle zum Abrufen von Daten aus einer Datenbank. Alle Methoden haben die Form (Pseudocode)

List<Typename> createTablenameList()

Methoden und Tabellen stehen nicht genau in einer 1-1-Beziehung, der Schwerpunkt liegt eher auf der Tatsache, dass Sie immer eine Art Liste erhalten, die aus einer Datenbank stammt.

TobiMcNamobi
quelle
12
Es fehlen relevante Informationen (welche Art von Methoden Sie haben). Wie auch immer, meine Vermutung: Wenn Sie nur durch Zahlen teilen, dann hat Ihr Kollege Recht, Sie verbessern nichts. Ein mögliches Teilungskriterium würde von der "Entität" (fast das Äquivalent einer Tabelle) zurückgegeben (also a UserDaound a CustomerDaound a ProductDao)
SJuan76
In der Tat sind einige Tabellen semantisch nah an anderen Tabellen, die "Cliquen" bilden. So auch die Methoden.
TobiMcNamobi
Ist es möglich, den Code zu sehen? Ich weiß, dass es 4 Jahre alt ist und Sie haben es wahrscheinlich inzwischen behoben: D Aber ich würde gerne über dieses Problem nachdenken. Ich habe so etwas schon einmal gelöst.
Clankill3r
@ clankill3r Tatsächlich habe ich keinen Zugriff mehr auf die spezifische Schnittstelle, die mich dazu gebracht hat, die obige Frage zu stellen. Das Problem ist jedoch allgemeiner. Stellen Sie sich eine List<WeatherDataRecord> createWeatherDataTable() {db.open(); return db.select("*", "tbl_weatherData");}
Datenbank

Antworten:

16

Ja, 50 Methoden sind ein Codegeruch, aber ein Codegeruch bedeutet, einen zweiten Blick darauf zu werfen, nicht dass er automatisch falsch ist. Wenn jeder Client, der diese Klasse verwendet, möglicherweise alle 50 Methoden benötigt, gibt es möglicherweise keinen Fall, um sie aufzuteilen. Dies ist jedoch unwahrscheinlich. Mein Punkt ist, dass das willkürliche Aufteilen einer Schnittstelle schlimmer sein kann, als sie überhaupt nicht aufzuteilen.

Es gibt kein einziges Muster, um das Problem zu beheben, aber das Prinzip, das den gewünschten Status beschreibt, ist das Prinzip der Schnittstellentrennung (das 'I' in SOLID), das besagt, dass kein Client gezwungen werden sollte, sich auf Methoden zu verlassen, die er nicht verwendet .

Die ISP-Beschreibung gibt Ihnen einen Hinweis, wie Sie das Problem beheben können: Sehen Sie sich den Client an . Wenn man sich nur eine Klasse ansieht, scheint es oft so, als ob alles zusammen gehört, aber klare Trennlinien entstehen, wenn man sich die Kunden ansieht , die diese Klasse verwenden. Berücksichtigen Sie beim Entwerfen einer Schnittstelle immer zuerst die Clients.

Die andere Möglichkeit zu bestimmen, ob und wo eine Schnittstelle aufgeteilt werden soll, besteht in einer zweiten Implementierung. Was häufig passiert, ist, dass Ihre zweite Implementierung nicht viele Methoden benötigt, daher sollten diese eindeutig in ihre eigene Schnittstelle aufgeteilt werden.

Karl Bielefeldt
quelle
Dies und die Antwort von utnapistim sind wirklich großartig.
TobiMcNamobi
13

Kollege: OK, Sie haben Recht, es ist nicht schön. Wie soll die Schnittstelle dann aussehen?

Ich: Wie wäre es mit 5 Methoden, die Objekte zurückgeben, die jeweils 10 Methoden haben?

Das sind keine guten Kriterien (in dieser Aussage gibt es eigentlich überhaupt keine Kriterien). Sie können sie gruppieren nach (vorausgesetzt, Ihre Anwendung ist für meine Beispiele eine Finanztransaktions-App):

  • Funktionalität (Einfügungen, Aktualisierungen, Auswahlen, Transaktionen, Metadaten, Schemata usw.)
  • Entität (Benutzer DAO , Einzahlungs-DAO usw.)
  • Anwendungsbereich (Finanztransaktionen, Benutzerverwaltung, Summen usw.)
  • Abstraktionsebene (der gesamte Tabellenzugriffscode ist ein separates Modul; alle ausgewählten APIs befinden sich in ihrer eigenen Hierarchie, die Transaktionsunterstützung ist separat, der gesamte Konvertierungscode in einem Modul, der gesamte Validierungscode in einem Modul usw.)

Mmmh, aber wäre das nicht dasselbe? Führt dies wirklich zu mehr Klarheit? Lohnt sich die Mühe?

Wenn Sie die richtigen Kriterien wählen, auf jeden Fall. Wenn nicht, definitiv nicht :).

Einige Beispiele:

utnapistim
quelle
3

Hin und wieder bin ich in einer Situation, in der ich eine Schnittstelle möchte und das erste, was mir in den Sinn kommt, ist eine große Schnittstelle. Gibt es dafür ein allgemeines Entwurfsmuster?

Es ist ein Design-Anti-Muster, das als monolithische Klasse bezeichnet wird . 50 Methoden in einer Klasse oder Schnittstelle zu haben, ist eine wahrscheinliche Verletzung des SRP . Die monolithische Klasse entsteht, weil sie versucht, für alle alles zu sein.

DCI- Adressen Methode aufblähen. Im Wesentlichen könnten die vielen Verantwortlichkeiten einer Klasse auf Rollen aufgeteilt (auf andere Klassen verlagert) werden, die nur in bestimmten Kontexten relevant sind. Die Anwendung von Rollen kann auf verschiedene Arten erreicht werden, einschließlich Mixins oder Dekorateure . Dieser Ansatz hält den Unterricht fokussiert und schlank.

Wie wäre es mit 5 Methoden, die Objekte zurückgeben, die jeweils 10 Methoden haben?

Dies schlägt vor, alle Rollen zu instanziieren, wenn das Objekt selbst instanziiert wird. Aber warum sollten Sie Rollen instanziieren, die Sie möglicherweise nicht benötigen? Instanziieren Sie stattdessen eine Rolle in dem Kontext, in dem Sie sie tatsächlich benötigen.

Wenn Sie feststellen, dass eine Umgestaltung in Richtung DCI nicht offensichtlich ist, können Sie ein einfacheres Besuchermuster wählen . Es bietet einen ähnlichen Vorteil, ohne die Erstellung von Anwendungsfallkontexten zu betonen.

EDIT: Mein Denken darüber hat einige geändert. Ich gab eine alternative Antwort.

Mario T. Lanza
quelle
1

Es scheint mir, dass jede andere Antwort den Punkt verfehlt. Der Punkt ist, dass eine Schnittstelle idealerweise einen atomaren Verhaltensblock definieren sollte. Das ist das Ich in SOLID.

Eine Klasse sollte eine Verantwortung haben, dies kann jedoch mehrere Verhaltensweisen umfassen. Um bei einem typischen Datenbank-Client-Objekt zu bleiben, bietet dies möglicherweise die volle CRUD-Funktionalität. Das wären vier Verhaltensweisen: Erstellen, Lesen, Aktualisieren und Löschen. In einer reinen SOLID-Welt würde der Datenbankclient nicht IDatabaseClient implementieren, sondern ICreator, IReader, IUpdater und IDeleter entfernen.

Dies hätte eine Reihe von Vorteilen. Wenn man nur die Klassendeklaration liest, lernt man sofort viel über die Klasse. Die von ihr implementierten Schnittstellen erzählen die ganze Geschichte. Zweitens, wenn das Client-Objekt als Argument übergeben werden soll, hat man jetzt verschiedene nützliche Optionen. Es könnte als IReader übergeben werden und man könnte sicher sein, dass der Angerufene nur lesen kann. Verschiedene Verhaltensweisen können separat getestet werden.

Beim Testen ist es jedoch üblich, eine Schnittstelle einfach auf eine Klasse zu legen, die eine 1: 1-Replik der vollständigen Klassenschnittstelle ist. Wenn Sie sich nur um das Testen kümmern, kann dies ein gültiger Ansatz sein. Es ermöglicht Ihnen, Dummies ziemlich schnell zu machen. Aber es ist kaum jemals FEST und wirklich ein Missbrauch von Schnittstellen für einen bestimmten Zweck.

Also ja, 50 Methoden sind ein Geruch, aber es hängt von der Absicht und dem Zweck ab, ob es schlecht ist oder nicht. Es ist sicherlich nicht ideal.

Martin Maat
quelle
0

In den Datenzugriffsschichten sind in der Regel viele Methoden an eine Klasse gebunden. Wenn Sie jemals mit Entity Framework oder anderen ORM-Tools gearbeitet haben, werden Sie feststellen, dass sie Hunderte von Methoden generieren. Ich gehe davon aus, dass Sie und Ihr Kollege es manuell implementieren. Es ist nicht notwendig, einen Code zu riechen, aber es ist nicht schön anzusehen. Ohne Ihre Domain zu kennen, ist es schwer zu sagen.

Roman Mik
quelle
Methoden oder Eigenschaften?
JeffO
0

Ich verwende Protokolle (nenne sie Schnittstellen, wenn du willst) fast universell für alle APIs mit FP und OOP. (Erinnern Sie sich an die Matrix? Es gibt keine Konkretionen!) Es gibt natürlich konkrete Typen, aber im Rahmen eines Programms wird jeder Typ als etwas angesehen, das in einem bestimmten Kontext eine Rolle spielt.

Dies bedeutet, dass Objekte, die in Programmen, in Funktionen usw. übergeben werden, als abstrakte Entitäten mit benannten Verhaltenssätzen betrachtet werden können. Man kann sich vorstellen, dass das Objekt eine Rolle spielt, bei der es sich um eine Reihe von Protokollen handelt. Eine Person (konkreter Typ) könnte ein Mann, ein Vater, ein Ehemann, ein Freund und ein Angestellter sein, aber ich kann mir nicht viele Funktionen vorstellen, die die Entität als die Summe von mehr als zwei davon betrachten würden.

Ich denke, es ist möglich, dass ein komplexes Objekt eine Reihe verschiedener Protokolle aushält, aber es würde Ihnen immer noch schwer fallen, zu einer API mit 50 Methoden zu gelangen. Die meisten Protokolle haben 1 oder 2 Methoden, vielleicht 3, aber niemals 50! Jede Entität mit 50 Methoden sollte aus einer Reihe kleinerer Komponenten mit jeweils eigenen Verantwortlichkeiten bestehen. Die gesamte Entität würde eine einfachere Schnittstelle darstellen, die die Gesamtsumme der darin enthaltenen Apis abstrahiert.

Anstatt in Objekten und Methoden zu denken, sollten Sie in Abstraktionen und Verträgen denken und welche Rolle ein Subjekt in einem bestimmten Kontext spielt.

Mario T. Lanza
quelle