Schnittstellen zu einer abstrakten Klasse

30

Mein Kollege und ich haben unterschiedliche Meinungen zum Verhältnis zwischen Basisklassen und Interfaces. Ich bin der Meinung, dass eine Klasse keine Schnittstelle implementieren sollte, es sei denn, diese Klasse kann verwendet werden, wenn eine Implementierung der Schnittstelle erforderlich ist. Mit anderen Worten, ich sehe Code gerne so:

interface IFooWorker { void Work(); }

abstract class BaseWorker {
    ... base class behaviors ...
    public abstract void Work() { }
    protected string CleanData(string data) { ... }
}

class DbWorker : BaseWorker, IFooWorker {
    public void Work() {
        Repository.AddCleanData(base.CleanData(UI.GetDirtyData()));
    }
}

Der DbWorker ist das, was die IFooWorker-Schnittstelle bekommt, weil es eine instantiierbare Implementierung der Schnittstelle ist. Es erfüllt den Vertrag vollständig. Mein Kollege bevorzugt das fast Identische:

interface IFooWorker { void Work(); }

abstract class BaseWorker : IFooWorker {
    ... base class behaviors ...
    public abstract void Work() { }
    protected string CleanData(string data) { ... }
}

class DbWorker : BaseWorker {
    public void Work() {
        Repository.AddCleanData(base.CleanData(UI.GetDirtyData()));
    }
}

Wo die Basisklasse die Schnittstelle erhält, und auf diese Weise gehören auch alle Erben der Basisklasse zu dieser Schnittstelle. Das nervt mich, aber ich kann keine konkreten Gründe nennen, warum die Basisklasse als Implementierung der Schnittstelle außerhalb von "nicht für sich allein stehen kann".

Was sind die Vor- und Nachteile seiner Methode im Vergleich zu meiner und warum sollte eine Methode einer anderen vorgezogen werden?

Bryan Boettcher
quelle
Ihr Vorschlag ähnelt sehr stark der Vererbung von Diamanten , was weiter unten zu großer Verwirrung führen kann.
Spoike
@Spoike: Wie sieht man das?
Niing
@Niing der Link, den ich im Kommentar bereitgestellt habe, hat ein einfaches Klassendiagramm und eine einfache einzeilige Erklärung, was das ist. Dies wird als "Diamant" -Problem bezeichnet, da die Vererbungsstruktur im Grunde genommen wie eine Diamantform gezeichnet ist (dh ♦ drawn).
Spoike
@Spoike: Warum wird diese Frage Diamant Vererbung verursachen?
Niing
1
@Niing: BaseWorker und IFooWorker mit derselben Methode zu erben, Workist das Problem der Diamantvererbung (in diesem Fall erfüllen beide einen Vertrag über die WorkMethode). In Java müssen Sie die Methoden der Schnittstelle implementieren, damit die Frage, welche WorkMethode das Programm verwenden soll, auf diese Weise vermieden wird. Sprachen wie C ++ disambiguieren dies jedoch nicht für Sie.
Spoike

Antworten:

24

Ich bin der Meinung, dass eine Klasse keine Schnittstelle implementieren sollte, es sei denn, diese Klasse kann verwendet werden, wenn eine Implementierung der Schnittstelle erforderlich ist.

BaseWorkererfüllt diese Anforderung. Nur weil Sie ein BaseWorkerObjekt nicht direkt instanziieren können, heißt das nicht, dass Sie keinen BaseWorker Zeiger haben können , der den Vertrag erfüllt. In der Tat ist das der springende Punkt bei abstrakten Klassen.

Es ist auch schwierig, anhand des von Ihnen veröffentlichten vereinfachten Beispiels zu sagen, aber ein Teil des Problems kann sein, dass die Schnittstelle und die abstrakte Klasse redundant sind. Sofern Sie keine anderen Klassen implementieren IFooWorker, die nicht von diesen abgeleitet sind BaseWorker, benötigen Sie die Schnittstelle überhaupt nicht. Schnittstellen sind nur abstrakte Klassen mit einigen zusätzlichen Einschränkungen, die die Mehrfachvererbung erleichtern.

Auch in einem vereinfachten Beispiel ist die Verwendung einer geschützten Methode, die sich explizit auf die Basis einer abgeleiteten Klasse bezieht, und das Fehlen einer eindeutigen Stelle zum Deklarieren der Schnittstellenimplementierung ein Warnsignal dafür, dass Sie die Vererbung nicht verwenden Zusammensetzung. Ohne dieses Erbe wird Ihre ganze Frage streitig.

Karl Bielefeldt
quelle
1
+1 für den Hinweis, dass BaseWorkereine gegebene BaseWorker myWorkernicht implementiert wird, nur weil sie nicht direkt instanziierbar ist IFooWorker.
Avner Shahar-Kashtan
2
Ich bin nicht einverstanden, dass Schnittstellen nur abstrakte Klassen sind. Abstrakte Klassen definieren normalerweise einige Implementierungsdetails (andernfalls wäre eine Schnittstelle verwendet worden). Wenn diese Angaben nicht Ihren Wünschen entsprechen, müssen Sie jetzt jede Verwendung der Basisklasse in etwas anderes ändern. Schnittstellen sind flach; Sie definieren die "Plug-and-Socket" -Beziehung zwischen Abhängigkeiten und abhängigen Personen. Als solche enge Kopplung an jedes Detail Implementierung ist kein Problem mehr. Wenn eine Klasse, die die Schnittstelle erbt, nicht Ihren Vorstellungen entspricht, entfernen Sie sie und fügen Sie etwas ganz anderes ein. Ihr Code könnte sich weniger interessieren.
KeithS
5
Sie haben beide Vorteile, aber normalerweise sollte die Schnittstelle immer für die Abhängigkeit verwendet werden, unabhängig davon, ob eine abstrakte Klasse vorhanden ist oder nicht, die Basisimplementierungen definiert. Die Kombination aus Schnittstelle und abstrakter Klasse bietet das Beste aus beiden Welten. Die Schnittstelle behält die flache Steckerbeziehung bei, während die abstrakte Klasse den nützlichen gemeinsamen Code bereitstellt. Sie können jede Ebene unterhalb der Schnittstelle nach Belieben entfernen und ersetzen.
KeithS
Mit "Zeiger" meinen Sie eine Instanz eines Objekts (auf das ein Zeiger verweisen würde)? Schnittstellen sind keine Klassen, sie sind Typen und können als unvollständiger Ersatz für die Mehrfachvererbung und nicht als Ersatz dienen. Sie enthalten also "Verhalten aus mehreren Quellen in einer Klasse".
Samis
46

Ich muss Ihrem Kollegen zustimmen.

In beiden Beispielen definiert BaseWorker die abstrakte Methode Work (), was bedeutet, dass alle Unterklassen den IFooWorker-Vertrag erfüllen können. In diesem Fall sollte BaseWorker die Schnittstelle implementieren, und diese Implementierung wird von den Unterklassen übernommen. Dies erspart Ihnen die explizite Angabe, dass jede Unterklasse tatsächlich ein IFooWorker (das DRY-Prinzip) ist.

Wenn Sie Work () nicht als Methode von BaseWorker definieren oder wenn IFooWorker andere Methoden hat, die Unterklassen von BaseWorker nicht benötigen oder wollen, müssen Sie (offensichtlich) angeben, welche Unterklassen IFooWorker tatsächlich implementieren.

Matthew Flynn
quelle
11
+1 hätte das Gleiche gepostet. Entschuldigung insta, aber ich denke, Ihr Mitarbeiter hat auch in diesem Fall Recht. Wenn etwas garantiert, dass ein Vertrag erfüllt wird, sollte er diesen Vertrag erben, unabhängig davon, ob die Garantie von einer Schnittstelle, einer abstrakten Klasse oder einer konkreten Klasse erfüllt wird. Einfach ausgedrückt: Der Bürge sollte den von ihm garantierten Vertrag erben.
Jimmy Hoffa
2
Ich stimme im Allgemeinen zu, möchte aber darauf hinweisen, dass Wörter wie "base", "standard", "default", "generic" usw. Code-Gerüche in einem Klassennamen sind. Wenn eine abstrakte Basisklasse fast den gleichen Namen wie eine Schnittstelle hat, jedoch eines dieser Wieselwörter enthalten ist, ist dies oft ein Zeichen für eine inkorrekte Abstraktion. Schnittstellen definieren, was etwas tut , Typvererbung definiert, was es ist . Wenn Sie ein IFoound ein BaseFoohaben, bedeutet dies, dass es sich entweder IFoonicht um eine entsprechend fein abgestimmte Schnittstelle handelt oder dass BaseFooeine Vererbung verwendet wird, bei der die Komposition möglicherweise die bessere Wahl ist.
Aaronaught
@Aaronaught Guter Punkt, auch wenn es sein kann, dass es wirklich etwas gibt, das alle oder die meisten Implementierungen von IFoo erben müssen (z. B. ein Handle für einen Dienst oder Ihre DAO-Implementierung).
Matthew Flynn
2
Sollte die Basisklasse dann nicht aufgerufen werden MyDaoFoooder SqlFoooder HibernateFoowas auch immer, um anzuzeigen, dass es sich nur um einen möglichen Baum von Klassen handelt, die implementiert werden IFoo? Es ist für mich noch riechender, wenn eine Basisklasse an eine bestimmte Bibliothek oder Plattform gekoppelt ist und dies im Namen nicht erwähnt wird ...
Aaronaught
@Aaronaught - Ja. Ich stimme vollkommen zu.
Matthew Flynn
11

Ich stimme in der Regel Ihrem Kollegen zu.

Nehmen wir Ihr Modell: Die Schnittstelle wird nur von den untergeordneten Klassen implementiert, obwohl die Basisklasse auch die Offenlegung von IFooWorker-Methoden erzwingt. Zunächst einmal ist es überflüssig; Unabhängig davon, ob die untergeordnete Klasse die Schnittstelle implementiert oder nicht, müssen sie die bereitgestellten Methoden von BaseWorker überschreiben. Ebenso muss jede IFooWorker-Implementierung die Funktionalität bereitstellen, unabhängig davon, ob sie Hilfe von BaseWorker erhalten oder nicht.

Dies macht Ihre Hierarchie zusätzlich mehrdeutig. "Alle IFooWorker sind BaseWorker" ist nicht unbedingt eine wahre Aussage, und "Alle BaseWorker sind IFooWorker" auch nicht. Wenn Sie also eine Instanzvariable, einen Parameter oder einen Rückgabetyp definieren möchten, bei dem es sich um eine Implementierung von IFooWorker oder BaseWorker handeln kann (wobei die allgemein verfügbare Funktionalität genutzt wird, die einer der Gründe ist, die an erster Stelle geerbt werden sollten), ist dies nicht der Fall diese sind garantiert allumfassend; Einige BaseWorker können keiner Variablen vom Typ IFooWorker zugewiesen werden und umgekehrt.

Das Modell Ihres Kollegen ist viel einfacher zu bedienen und zu ersetzen. "Alle BaseWorker sind IFooWorker" ist jetzt eine wahre Aussage; Sie können jeder IFooWorker-Abhängigkeit jede BaseWorker-Instanz zuweisen, ohne Probleme. Die gegenteilige Aussage "Alle IFooWorker sind BaseWorker" ist nicht wahr; Dadurch können Sie BaseWorker durch BetterBaseWorker ersetzen, und Ihr konsumierender Code (der von IFooWorker abhängt) muss den Unterschied nicht feststellen.

KeithS
quelle
Ich mag den Klang dieser Antwort, folge aber nicht ganz dem letzten Teil. Sie schlagen vor, dass BetterBaseWorker eine unabhängige abstrakte Klasse ist, die IFooWorker implementiert, sodass mein hypothetisches Plugin einfach sagen könnte: "Oh Mann! Der Better Base Worker ist endlich da!" und alle BaseWorker-Verweise auf BetterBaseWorker ändern und es einen Tag nennen (vielleicht mit den coolen neuen Rückgabewerten spielen, mit denen ich große Teile meines redundanten Codes usw. entfernen kann)?
Anthony
Mehr oder weniger, ja. Der große Vorteil ist, dass Sie die Anzahl der Verweise auf BaseWorker reduzieren, die geändert werden müssen, um stattdessen BetterBaseWorker zu werden. Der Großteil Ihres Codes verweist nicht direkt auf die konkrete Klasse, sondern verwendet IFooWorker. Wenn Sie also ändern, was diesen IFooWorker-Abhängigkeiten zugewiesen ist (Eigenschaften von Klassen, Parameter von Konstruktoren oder Methoden), sollte der Code, der IFooWorker verwendet, keinen Unterschied feststellen im einsatz.
KeithS
6

Ich muss diesen Antworten eine Warnung hinzufügen. Das Koppeln der Basisklasse an die Schnittstelle erzeugt eine Kraft in der Struktur dieser Klasse. In Ihrem grundlegenden Beispiel ist es ein Kinderspiel, dass die beiden gekoppelt werden sollten, aber das kann nicht in allen Fällen zutreffen.

Nehmen Sie an Javas Klassen für das Collection Framework teil:

abstract class AbstractList
class LinkedList extends AbstractList implements Queue

Die Tatsache, dass der Queue- Vertrag von LinkedList implementiert wird , hat das Anliegen nicht in AbstractList verdrängt .

Was ist der Unterschied zwischen den beiden Fällen? Der Zweck von BaseWorker war immer (wie durch seinen Namen und seine Schnittstelle mitgeteilt), Operationen in IWorker zu implementieren . Der Zweck von AbstractList und der von Queue sind unterschiedlich, aber ein Nachkomme des ersteren kann den letztgenannten Vertrag noch umsetzen.

Mihai Danila
quelle
Das passiert die halbe Zeit. Er zieht es immer vor, die Schnittstelle in der Basisklasse zu implementieren, und ich ziehe es immer vor, sie im endgültigen Beton zu implementieren. Das Szenario, das Sie vorgestellt haben, kommt häufig vor und ist Teil der Gründe, warum Schnittstellen auf der Basis mich stören.
Bryan Boettcher
1
Richtig, insta, und ich betrachte die Verwendung von Schnittstellen auf diese Weise als einen Aspekt der Übernutzung durch Vererbung, die wir in vielen Entwicklungsumgebungen sehen. Wie die GoF in ihrem Entwurfsmusterbuch sagte, ziehen Sie die Komposition der Vererbung vor , und es ist eine der Möglichkeiten, genau dieses Prinzip zu fördern, die Schnittstelle von der Basisklasse fernzuhalten.
Mihai Danila
1
Ich erhalte die Warnung, aber Sie werden feststellen, dass die abstrakte Klasse des OP abstrakte Methoden enthielt, die mit der Schnittstelle übereinstimmen: Ein BaseWorker ist implizit ein IFooWorker. Indem man es explizit macht, wird diese Tatsache nützlicher. In Queue sind zahlreiche Methoden enthalten, die jedoch nicht in AbstractList enthalten sind. Daher ist eine AbstractList keine Queue, selbst wenn es sich um untergeordnete Methoden handelt. AbstractList und Queue sind orthogonal.
Matthew Flynn
Vielen Dank für diesen Einblick; Ich stimme zu, dass der Fall des OP in diese Kategorie fällt, aber ich spürte, dass es eine größere Diskussion auf der Suche nach einer tieferen Regel gab, und ich wollte meine Beobachtungen über eine Tendenz teilen, über die ich gelesen habe (und die ich selbst hatte) für eine kurze Zeit) der Übernutzung der Vererbung, möglicherweise, weil dies eines der Werkzeuge in der OOP-Toolbox ist.
Mihai Danila
Verdammt, es sind sechs Jahre vergangen. :)
Mihai Danila
2

Ich würde die Frage stellen, was passiert, wenn Sie IFooWorkereine neue Methode hinzufügen?

Wenn BaseWorkerdie Schnittstelle implementiert ist, muss sie standardmäßig die neue Methode deklarieren, auch wenn sie abstrakt ist. Wenn die Schnittstelle dagegen nicht implementiert wird, treten nur Kompilierungsfehler für die abgeleiteten Klassen auf.

Aus diesem Grund würde ich die Basisklasse veranlassen, die Schnittstelle zu implementieren , da ich möglicherweise alle Funktionen für die neue Methode in der Basisklasse implementieren kann , ohne die abgeleiteten Klassen zu berühren.

Scott Whitlock
quelle
1

Stellen Sie sich zunächst vor, was eine abstrakte Basisklasse und was eine Schnittstelle ist. Überlegen Sie, wann Sie das eine oder andere verwenden würden und wann nicht.

Es ist üblich, dass die Leute denken, dass beide Konzepte sehr ähnlich sind. Tatsächlich handelt es sich um eine häufig gestellte Interviewfrage (der Unterschied zwischen den beiden ist ??).

Eine abstrakte Basisklasse gibt Ihnen also etwas, was Interfaces nicht können, nämlich Standardimplementierungen von Methoden. (Es gibt noch andere Dinge, in C # können Sie statische Methoden für eine abstrakte Klasse verwenden, beispielsweise nicht für eine Schnittstelle.)

Dies wird beispielsweise häufig mit IDisposable verwendet. Die abstrakte Klasse implementiert die Dispose-Methode von IDisposable. Dies bedeutet, dass alle abgeleiteten Versionen der Basisklasse automatisch verfügbar sind. Sie können dann mit mehreren Optionen spielen. Machen Sie Dispose abstrakt und zwingen Sie abgeleitete Klassen, es zu implementieren. Stellen Sie eine virtuelle Standardimplementierung bereit, damit diese nicht ausgeführt wird, oder machen Sie sie weder virtuell noch abstrakt, und lassen Sie sie virtuelle Methoden aufrufen, die beispielsweise BeforeDispose, AfterDispose, OnDispose oder Disposing heißen.

Jedes Mal, wenn alle abgeleiteten Klassen das Interface unterstützen müssen, wird es auf die Basisklasse angewendet. Wenn nur eine oder einige diese Schnittstelle benötigen, wird sie für die abgeleitete Klasse verwendet.

Das ist eigentlich alles eine grobe Vereinfachung. Eine andere Alternative besteht darin, dass abgeleitete Klassen die Schnittstellen nicht einmal implementieren, sondern über ein Adaptermuster bereitstellen. Ein Beispiel dafür, das ich kürzlich gesehen habe, war in IObjectContextAdapter.

Ian
quelle
1

Ich bin noch Jahre davon entfernt, die Unterscheidung zwischen einer abstrakten Klasse und einer Schnittstelle zu verstehen. Jedes Mal, wenn ich denke, die Grundkonzepte in den Griff zu bekommen, schaue ich auf StackExchange und bin zwei Schritte zurück. Aber ein paar Gedanken zum Thema und zur OP-Frage:

Zuerst:

Es gibt zwei allgemeine Erklärungen für eine Schnittstelle:

  1. Eine Schnittstelle ist eine Liste von Methoden und Eigenschaften, die jede Klasse implementieren kann. Durch die Implementierung einer Schnittstelle garantiert eine Klasse, dass diese Methoden (und ihre Signaturen) und diese Eigenschaften (und ihre Typen) verfügbar sind, wenn eine "Schnittstelle" mit dieser Klasse oder erstellt wird ein Objekt dieser Klasse. Eine Schnittstelle ist ein Vertrag.

  2. Schnittstellen sind abstrakte Klassen, die nichts tun / können. Sie sind nützlich, weil Sie im Gegensatz zu diesen mittleren Elternklassen mehr als eine implementieren können. Ich könnte ein Objekt der Klasse BananaBread sein und von BaseBread erben, aber das heißt nicht, dass ich nicht auch die Schnittstellen IWithNuts und ITastesYummy implementieren kann. Ich könnte sogar die IDoesTheDishes-Schnittstelle implementieren, weil ich nicht nur Brot bin, weißt du?

Es gibt zwei allgemeine Erklärungen für eine abstrakte Klasse:

  1. Eine abstrakte Klasse ist das , was das Ding nicht kann. Es ist wie die Essenz, überhaupt keine echte Sache. Warte, das wird helfen. Ein Boot ist eine abstrakte Klasse, aber eine sexy Playboy-Yacht wäre eine Unterklasse von BaseBoat.

  2. Ich habe ein Buch über abstrakte Klassen gelesen, und vielleicht sollten Sie dieses Buch lesen, weil Sie es wahrscheinlich nicht verstehen und es falsch machen, wenn Sie dieses Buch nicht gelesen haben.

Übrigens, die Buchziter wirken immer beeindruckend, auch wenn ich immer noch verwirrt davongehe.

Zweite:

Auf SO fragte jemand eine einfachere Version dieser Frage, den Klassiker: "Warum Schnittstellen verwenden? Was ist der Unterschied? Was fehle ich?" In einer Antwort wurde ein Luftwaffenpilot als einfaches Beispiel verwendet. Es landete nicht ganz, aber es löste einige großartige Kommentare aus, von denen einer die IFlyable-Schnittstelle mit Methoden wie takeOff, pilotEject usw. erwähnte. aber entscheidend. Eine Schnittstelle macht ein Objekt / eine Klasse intuitiv oder gibt zumindest den Sinn, den es hat. Eine Schnittstelle dient nicht dem Nutzen des Objekts oder der Daten, sondern etwas, das mit diesem Objekt interagieren muss. Der Klassiker Obst-> Apfel-> Fuji oder Form-> Dreieck-> Gleichseitige Beispiele für die Vererbung sind ein hervorragendes Modell für das taxonomische Verständnis eines bestimmten Objekts anhand seiner Nachkommen. Es informiert den Verbraucher und den Verarbeiter über seine allgemeinen Eigenschaften, Verhaltensweisen, ob es sich bei dem Objekt um eine Gruppe von Dingen handelt, wird Ihr System beschädigen, wenn Sie es an der falschen Stelle ablegen, oder es beschreibt sensorische Daten, einen Konnektor zu einem bestimmten Datenspeicher oder Finanzdaten für die Gehaltsabrechnung.

Ein bestimmtes Flugzeugobjekt kann eine Methode für eine Notlandung haben oder nicht, aber ich bin sauer, wenn ich davon ausgehe, dass das Notland wie jedes andere flugfähige Objekt in DumbPlane aufgewacht ist, und erfahre, dass die Entwickler Quickland gewählt haben weil sie dachten, dass es egal sei. Genauso wie ich frustriert wäre, wenn jeder Schraubenhersteller eine eigene Interpretation von Righty Tighty hätte oder wenn mein Fernseher nicht den Lautstärkeregler über dem Lautstärkeregler hätte.

Abstrakte Klassen sind das Modell, das festlegt, was ein untergeordnetes Objekt als diese Klasse qualifizieren muss. Wenn Sie nicht alle Qualitäten einer Ente haben, spielt es keine Rolle, dass Sie das IQuack-Interface implementiert haben, Ihr einziger komischer Pinguin. Schnittstellen sind die Dinge, die Sinn machen, auch wenn Sie sich bei nichts anderem sicher sind. Jeff Goldblum und Starbuck waren beide in der Lage, außerirdische Raumschiffe zu fliegen, da die Benutzeroberfläche zuverlässig ähnlich war.

Dritte:

Ich stimme Ihrem Kollegen zu, denn manchmal müssen Sie bestimmte Methoden von Anfang an durchsetzen. Wenn Sie ein Active Record-ORM erstellen, ist eine Speichermethode erforderlich. Dies hängt nicht von der Unterklasse ab, die instanziiert werden kann. Und wenn die ICRUD-Schnittstelle portabel genug ist, um nicht ausschließlich mit einer abstrakten Klasse gekoppelt zu werden, kann sie von anderen Klassen implementiert werden, um sie für jeden, der bereits mit einer der abgeleiteten Klassen dieser abstrakten Klasse vertraut ist, zuverlässig und intuitiv zu machen.

Auf der anderen Seite gab es bereits ein gutes Beispiel dafür, wann nicht eine Schnittstelle an die abstrakte Klasse gebunden werden sollte, da nicht alle Listentypen eine Warteschlangenschnittstelle implementieren (oder sollten). Sie sagten, dass dieses Szenario die Hälfte der Zeit passiert, was bedeutet, dass Sie und Ihr Kollege beide die Hälfte der Zeit falsch liegen. Daher ist es das Beste, zu argumentieren, zu debattieren, zu überlegen und die Kopplung zu akzeptieren, wenn sie sich als richtig herausstellt . Werden Sie jedoch kein Entwickler, der einer Philosophie folgt, auch wenn diese für den jeweiligen Job nicht die beste ist.

Anthony
quelle
0

Es gibt noch einen weiteren Aspekt, warum das DbWorkerimplementiert werden sollte IFooWorker, wie Sie vorschlagen.

Im Falle des Stils Ihres Mitarbeiters verliert die Schnittstelle , wenn aus irgendeinem Grund ein späteres Refactoring stattfindet und davon DbWorkerausgegangen wird, dass die abstrakte BaseWorkerKlasse nicht erweitert wird. Dies könnte, muss aber nicht, Auswirkungen auf die Kunden haben, die es verbrauchen, wenn sie die Schnittstelle erwarten .DbWorkerIFooWorkerIFooWorker

Frederik Krautwald
quelle
-1

Zu Beginn definiere ich Interfaces und abstrakte Klassen gerne anders:

Interface: a contract that each class must fulfill if they are to implement that interface
Abstract class: a general class (i.e vehicle is an abstract class, while porche911 is not)

In Ihrem Fall setzen alle Mitarbeiter um, work()weil der Vertrag dies von ihnen verlangt. Daher sollte Ihre Basisklasse Baseworkerdiese Methode entweder explizit oder als virtuelle Funktion implementieren. Ich würde vorschlagen, dass Sie diese Methode in Ihre Basisklasse aufnehmen, da alle Worker dies tun müssen work(). Daher ist es logisch, dass Ihre Basisklasse (obwohl Sie keinen Zeiger dieses Typs erstellen können) diese als Funktion enthält.

DbWorkerBefriedigt jetzt die IFooWorkerSchnittstelle, aber wenn es eine bestimmte Art gibt, die diese Klasse tut work(), muss sie die Definition, von der sie geerbt wurde, wirklich überladen BaseWorker. Der Grund, warum es die Schnittstelle nicht IFooWorkerdirekt implementieren sollte, ist, dass dies nichts Besonderes ist DbWorker. Wenn Sie dies jedes Mal tun, wenn Sie ähnliche Klassen implementieren DbWorker, verletzen Sie DRY .

Wenn zwei Klassen dieselbe Funktion auf unterschiedliche Weise implementieren, können Sie nach der größten gemeinsamen Superklasse suchen. Meistens werden Sie einen finden. Wenn nicht, schauen Sie weiter oder geben Sie auf und akzeptieren Sie, dass sie nicht genug Gemeinsamkeiten haben, um eine Basisklasse zu bilden.

Arnab Datta
quelle
-3

Wow, all diese Antworten und keine, die darauf hinweisen, dass die Schnittstelle im Beispiel überhaupt keinen Zweck erfüllt. Sie verwenden keine Vererbung und keine Schnittstelle für dieselbe Methode, da dies sinnlos ist. Sie benutzen den einen oder anderen. In diesem Forum gibt es viele Fragen mit Antworten, in denen die Vorteile der einzelnen und der jeweils erforderlichen Szenarien erläutert werden.

Martin Maat
quelle
3
Das ist eine Patentlüge. Die Schnittstelle ist ein Vertrag, nach dem sich das System unabhängig von der Implementierung verhalten soll. Die geerbte Basisklasse ist eine von vielen möglichen Implementierungen für diese Schnittstelle. Ein ausreichend großes System verfügt über mehrere Basisklassen, die die verschiedenen Schnittstellen erfüllen (in der Regel mit Generika geschlossen). Genau das hat dieses Setup bewirkt und es hat sich als hilfreich erwiesen, sie zu teilen.
Bryan Boettcher
Aus OOP-Sicht ist es ein wirklich schlechtes Beispiel. Wenn Sie sagen "die Basisklasse ist nur eine Implementierung" (es sollte sein oder es würde keinen Sinn für die Schnittstelle geben), sagen Sie, dass es Nicht-Worker-Klassen geben wird, die die IFooWorker-Schnittstelle implementieren. Dann werden Sie eine Basisklasse haben, die ein spezialisierter Mitarbeiter ist (wir sollten davon ausgehen, dass es auch Bararbeiter gibt), und Sie werden andere Mitarbeiter haben, die nicht einmal einfache Mitarbeiter sind! Das ist rückwärts. In mehr als einer Hinsicht.
Martin Maat
1
Vielleicht stecken Sie beim Wort "Worker" in der Benutzeroberfläche fest. Was ist mit "Datasource"? Wenn wir eine IDatasource <T> haben, können wir trivialerweise eine SqlDatasource <T>, RestDatasource <T>, MongoDatasource <T> haben. Das Unternehmen entscheidet, welche Abschlüsse der generischen Schnittstelle in die Abschlussimplementierungen der abstrakten Datenquellen übernommen werden.
Bryan Boettcher
Es kann sinnvoll sein, die Vererbung nur für DRY zu verwenden und eine allgemeine Implementierung in eine Basisklasse zu integrieren. Sie würden jedoch keine überschriebenen Methoden an Schnittstellenmethoden ausrichten, da die Basisklasse allgemeine Unterstützungslogik enthalten würde. Wenn sie in einer Reihe stehen würden, würde dies zeigen, dass die Vererbung Ihr Problem in Ordnung bringt, und die Schnittstelle würde keinen Zweck erfüllen. Sie verwenden eine Schnittstelle in Fällen, in denen die Vererbung Ihr Problem nicht löst, da die gemeinsamen Merkmale, die Sie modellieren möchten, nicht zu einem Singke-Klassenbaum passen. Und ja, die Formulierung im Beispiel macht es umso falscher.
Martin Maat