Ist "Wenn eine Methode ohne Änderungen wiederverwendet wird, fügen Sie die Methode in eine Basisklasse ein, andernfalls erstellen Sie eine Schnittstelle" eine gute Faustregel?

10

Ein Kollege von mir hat eine Faustregel für die Auswahl zwischen der Erstellung einer Basisklasse oder einer Schnittstelle entwickelt.

Er sagt:

Stellen Sie sich jede neue Methode vor, die Sie implementieren möchten. Für jeden von ihnen, bedenken Sie: Diese Methode wird von mehr als einer Klasse implementiert werden , in genau dieser Form ohne jede Veränderung? Wenn die Antwort "Ja" lautet, erstellen Sie eine Basisklasse. Erstellen Sie in jeder anderen Situation eine Schnittstelle.

Zum Beispiel:

Betrachten Sie die Klassen catund dog, die die Klasse erweitern mammalund eine einzige Methode haben pet(). Wir fügen dann die Klasse hinzu alligator, die nichts erweitert und eine einzige Methode hat slither().

Jetzt möchten wir eat()allen eine Methode hinzufügen .

Wenn die Implementierung der eat()Methode für und genau gleich catist , sollten wir eine Basisklasse (z. B. ) erstellen , die diese Methode implementiert.dogalligatoranimal

Wenn sich die Implementierung jedoch im alligatorgeringsten unterscheidet, sollten wir eine IEatSchnittstelle erstellen mammalund alligatordiese erstellen und implementieren.

Er besteht darauf, dass diese Methode alle Fälle abdeckt, aber es scheint mir eine übermäßige Vereinfachung zu sein.

Lohnt es sich, diese Faustregel zu befolgen?

sbichenko
quelle
27
alligatorDie Implementierung von eatunterscheidet sich natürlich darin, dass sie catund dogals Parameter akzeptiert .
Sbichenko
1
Wenn Sie wirklich möchten, dass eine abstrakte Basisklasse Implementierungen gemeinsam nutzt, aber Schnittstellen für die korrekte Erweiterbarkeit verwenden sollten, können Sie stattdessen häufig Merkmale verwenden. Das heißt, wenn Ihre Sprache dies unterstützt.
Amon
2
Wenn Sie sich in eine Ecke malen, ist es am besten, in der Ecke zu sein, die der Tür am nächsten liegt.
JeffO
10
Der größte Fehler, den die Leute machen, ist zu glauben, dass Schnittstellen einfach leere abstrakte Klassen sind. Eine Schnittstelle ist eine Möglichkeit für einen Programmierer zu sagen: "Es ist mir egal, was Sie mir geben, solange es dieser Konvention folgt." Die Wiederverwendung von Code sollte dann (idealerweise) durch Komposition erfolgen. Ihr Mitarbeiter ist falsch.
Riwalk
2
Ein CS-Proff von mir lehrte, dass Superklassen sein sollten is aund Schnittstellen sind acts likeoder is. Also ein is aHundesäugetier und acts likeein Esser. Dies würde uns sagen, dass Säugetiere eine Klasse und Esser eine Schnittstelle sein sollten. Es war schon immer eine sehr hilfreiche Anleitung. Nebenbemerkung: Ein Beispiel iswäre The cake is eatableoder The book is writable.
MirroredFate

Antworten:

13

Ich denke nicht, dass dies eine gute Faustregel ist. Wenn Sie sich Gedanken über die Wiederverwendung von Code machen, können Sie eine PetEatingBehaviorRolle implementieren, die darin besteht, die Essfunktionen für Katzen und Hunde zu definieren. Dann können Sie IEatCode gemeinsam wiederverwenden.

In diesen Tagen sehe ich immer weniger Gründe, Vererbung zu verwenden. Ein großer Vorteil ist die Benutzerfreundlichkeit. Nehmen Sie ein GUI-Framework. Eine beliebte Methode zum Entwerfen einer API hierfür besteht darin, eine große Basisklasse verfügbar zu machen und zu dokumentieren, welche Methoden der Benutzer überschreiben kann. So können wir alle anderen komplexen Dinge ignorieren, die unsere Anwendung verwalten muss, da die Basisklasse eine "Standard" -Implementierung vorgibt. Aber wir können die Dinge trotzdem anpassen, indem wir die richtige Methode bei Bedarf neu implementieren.

Wenn Sie sich an die Benutzeroberfläche für Ihre API halten, hat Ihr Benutzer normalerweise mehr Arbeit zu erledigen, damit einfache Beispiele funktionieren. APIs mit Schnittstellen sind jedoch häufig weniger gekoppelt und aus dem einfachen Grund einfacher zu warten, da sie IEatweniger Informationen enthalten als die MammalBasisklasse. Die Verbraucher sind also auf einen schwächeren Vertrag angewiesen, Sie erhalten mehr Refactoring-Möglichkeiten usw.

Um Rich Hickey zu zitieren: Einfach! = Einfach

Simon Bergot
quelle
Könnten Sie ein grundlegendes Beispiel für die PetEatingBehaviorImplementierung liefern ?
Sbichenko
1
@exizt Erstens bin ich schlecht in der Auswahl von Namen. Also PetEatingBehaviorist wahrscheinlich der falsche. Ich kann Ihnen keine Implementierung geben, da dies nur ein Spielzeugbeispiel ist. Aber ich kann Refactoring-Schritte beschreiben: Es gibt einen Konstruktor, der alle Informationen verwendet, die von Katzen / Hunden verwendet werden, um die eatMethode zu definieren (Zahninstanz, Mageninstanz usw.). Es enthält nur den Code, der Katzen und Hunden gemeinsam ist, die in ihrer eatMethode verwendet werden. Sie müssen lediglich ein PetEatingBehaviorMitglied erstellen und die Aufrufe an dog.eat/cat.eat weiterleiten.
Simon Bergot
Ich kann nicht glauben, dass dies die einzige Antwort von vielen ist, die das OP von Vererbungen
ablenkt
1
@DannyTuppeny Warum nicht?
Sbichenko
4
@exizt Das Erben von einer Klasse ist eine große Sache; Sie nehmen eine (wahrscheinlich dauerhafte) Abhängigkeit von etwas an, von dem Sie in vielen Sprachen nur eine haben können. Die Wiederverwendung von Code ist eine ziemlich triviale Sache, um diese oft nur Basisklasse zu verwenden. Was ist, wenn Sie Methoden haben, bei denen Sie sie für eine andere Klasse freigeben möchten, diese jedoch bereits eine Basisklasse haben, weil andere Methoden wiederverwendet werden (oder sogar, dass sie Teil einer "echten" Hierarchie sind, die polymorph verwendet wird (?)). Verwenden Sie die Vererbung, wenn ClassA eine Art von ClassB ist (z. B. ein Auto erbt von Vehicle), und nicht, wenn es einige interne Implementierungsdetails teilt :-)
Danny Tuppeny
11

Lohnt es sich, diese Faustregel zu befolgen?

Es ist eine anständige Faustregel, aber ich kenne einige Stellen, an denen ich dagegen verstoße.

Um fair zu sein, benutze ich viel mehr (abstrakte) Basisklassen als meine Kollegen. Ich mache das als defensive Programmierung.

Für mich (in vielen Sprachen) fügt eine abstrakte Basisklasse die Schlüsseleinschränkung hinzu, dass es möglicherweise nur eine Basisklasse gibt. Wenn Sie sich für die Verwendung einer Basisklasse anstelle einer Schnittstelle entscheiden, sagen Sie: "Dies ist die Kernfunktionalität der Klasse (und jedes Erben), und es ist unangemessen, sie ohne weiteres mit anderen Funktionen zu mischen." Schnittstellen sind das Gegenteil: "Dies ist ein Merkmal eines Objekts, nicht unbedingt seine Kernverantwortung".

Diese Art der Entwurfsauswahl erstellt implizite Anleitungen für Ihre Implementierer, wie sie Ihren Code verwenden sollen, und schreibt im weiteren Sinne ihren Code. Aufgrund ihrer größeren Auswirkung auf die Codebasis neige ich dazu, diese Dinge bei der Entwurfsentscheidung stärker zu berücksichtigen.

Telastyn
quelle
1
Wenn ich diese Antwort 3 Jahre später noch einmal lese, finde ich, dass dies ein großartiger Punkt ist: Wenn Sie sich für die Verwendung einer Basisklasse anstelle einer Schnittstelle entscheiden, sagen Sie: "Dies ist die Kernfunktionalität der Klasse (und jedes Erben), und das ist es auch." unangemessen, wohl oder übel mit anderen Funktionen zu mischen "
sbichenko
9

Das Problem bei der Vereinfachung Ihres Freundes besteht darin, dass es außerordentlich schwierig ist, festzustellen, ob eine Methode jemals geändert werden muss oder nicht. Es ist besser, eine Regel zu verwenden, die die Mentalität hinter Oberklassen und Schnittstellen anspricht.

Anstatt herauszufinden, was Sie tun sollten, indem Sie sich ansehen, was Ihrer Meinung nach die Funktionalität ist, schauen Sie sich die Hierarchie an, die Sie erstellen möchten.

So hat ein Professor von mir den Unterschied gelehrt:

Superklassen sollten sein is aund Schnittstellen sind acts likeoder is. Also ein is aHundesäugetier und acts likeein Esser. Dies würde uns sagen, dass Säugetiere eine Klasse und Esser eine Schnittstelle sein sollten. Ein Beispiel iswäre The cake is eatableoder The book is writable(beide eatableund writableSchnittstellen machen).

Die Verwendung einer solchen Methodik ist relativ einfach und führt dazu, dass Sie nach der Struktur und den Konzepten codieren und nicht nach dem, was der Code Ihrer Meinung nach bewirken wird. Dies erleichtert die Wartung, die Lesbarkeit des Codes und die Erstellung des Designs.

Unabhängig davon, was der Code tatsächlich aussagt, könnten Sie in fünf Jahren zurückkehren und dieselbe Methode verwenden, um Ihre Schritte nachzuvollziehen und auf einfache Weise herauszufinden, wie Ihr Programm entworfen wurde und wie es interagiert.

Nur meine zwei Cent.

MirroredFate
quelle
6

Auf Wunsch von exizt erweitere ich meinen Kommentar zu einer längeren Antwort.

Der größte Fehler, den die Leute machen, ist zu glauben, dass Schnittstellen einfach leere abstrakte Klassen sind. Eine Schnittstelle ist eine Möglichkeit für einen Programmierer zu sagen: "Es ist mir egal, was Sie mir geben, solange es dieser Konvention folgt."

Die .NET-Bibliothek dient als wunderbares Beispiel. Wenn Sie beispielsweise eine Funktion schreiben, die eine akzeptiert IEnumerable<T>, sagen Sie: "Es ist mir egal, wie Sie Ihre Daten speichern. Ich möchte nur wissen, dass ich eine foreachSchleife verwenden kann.

Dies führt zu einem sehr flexiblen Code. Um integriert zu werden, müssen Sie plötzlich nur noch die Regeln der vorhandenen Schnittstellen einhalten. Wenn die Implementierung der Schnittstelle schwierig oder verwirrend ist, ist dies möglicherweise ein Hinweis darauf, dass Sie versuchen, einen quadratischen Stift in ein rundes Loch zu schieben.

Aber dann stellt sich die Frage: "Was ist mit der Wiederverwendung von Code? Meine CS-Professoren sagten mir, dass die Vererbung die Lösung für alle Probleme bei der Wiederverwendung von Code ist und dass die Vererbung es Ihnen ermöglicht, einmal zu schreiben und überall zu verwenden, und Menangitis bei der Rettung von Waisenkindern heilen würde von den aufsteigenden Meeren und es würde keine Tränen mehr geben und weiter und weiter usw. usw. usw. "

Die Verwendung der Vererbung, nur weil Sie den Klang der Wörter "Wiederverwendung von Code" mögen, ist eine ziemlich schlechte Idee. Der Code-Styling-Leitfaden von Google macht diesen Punkt ziemlich präzise:

Zusammensetzung ist oft angemessener als Vererbung. ... [B] Da der Code, der eine Unterklasse implementiert, zwischen der Basis und der Unterklasse verteilt ist, kann es schwieriger sein, eine Implementierung zu verstehen. Die Unterklasse kann keine Funktionen überschreiben, die nicht virtuell sind, sodass die Unterklasse die Implementierung nicht ändern kann.

Um zu veranschaulichen, warum Vererbung nicht immer die Antwort ist, verwende ich eine Klasse namens MySpecialFileWriter †. Eine Person, die blindlings glaubt, dass Vererbung die Lösung für alle Probleme ist, würde argumentieren, dass Sie versuchen sollten, von zu erben FileStream, damit Sie FileStreamden Code nicht duplizieren . Kluge Leute erkennen, dass das dumm ist. Sie sollten nur ein FileStreamObjekt in Ihrer Klasse haben (entweder als lokale oder als Mitgliedsvariable) und dessen Funktionalität verwenden.

Das FileStreamBeispiel mag erfunden erscheinen, ist es aber nicht. Wenn Sie zwei Klassen haben, die beide dieselbe Schnittstelle auf genau dieselbe Weise implementieren, sollten Sie eine dritte Klasse haben, die jede duplizierte Operation kapselt. Ihr Ziel sollte es sein, Klassen zu schreiben, die in sich geschlossene wiederverwendbare Blöcke sind, die wie Legos zusammengestellt werden können.

Dies bedeutet nicht, dass Vererbung um jeden Preis vermieden werden sollte. Es gibt viele Punkte zu beachten, und die meisten werden durch die Untersuchung der Frage "Zusammensetzung vs. Vererbung" abgedeckt. Unser eigener Stapelüberlauf hat einige gute Antworten zu diesem Thema.

Letztendlich fehlt den Gefühlen Ihres Mitarbeiters die Tiefe oder das Verständnis, die erforderlich sind, um eine fundierte Entscheidung zu treffen. Erforschen Sie das Thema und finden Sie es selbst heraus.

† Bei der Veranschaulichung der Vererbung verwendet jeder Tiere. Das ist nutzlos. In 11 Jahren Entwicklungszeit habe ich noch nie eine Klasse mit dem Namen geschrieben Cat, daher werde ich sie nicht als Beispiel verwenden.

Riwalk
quelle
Wenn ich Sie richtig verstehe, bietet die Abhängigkeitsinjektion dieselben Funktionen zur Wiederverwendung von Code wie die Vererbung. Aber wofür würden Sie dann die Vererbung verwenden? Würden Sie einen Anwendungsfall angeben? (Ich wirklich zu schätzen das mit FileStream)
sbichenko
1
@exizt, nein, es bietet nicht die gleichen Funktionen zur Wiederverwendung von Code. Es bietet (in vielen Fällen) bessere Funktionen zur Wiederverwendung von Code, da Implementierungen gut enthalten sind. Klassen werden explizit über ihre Objekte und ihre öffentliche API interagiert, anstatt implizit Funktionen zu erlangen und die Hälfte der Funktionalität durch Überladen vorhandener Funktionen vollständig zu ändern.
Riwalk
4

Beispiele machen selten einen Punkt, besonders nicht, wenn es um Autos oder Tiere geht. Die Art und Weise, wie ein Tier frisst (oder Nahrung verarbeitet), ist nicht wirklich an sein Erbe gebunden. Es gibt keine einzige Art zu essen, die für alle Tiere gilt. Dies hängt mehr von seinen physischen Fähigkeiten ab (sozusagen von der Art der Eingabe, Verarbeitung und Ausgabe). Säugetiere können beispielsweise Fleischfresser, Pflanzenfresser oder Allesfresser sein, während Vögel, Säugetiere und Fische Insekten fressen können. Dieses Verhalten kann mit bestimmten Mustern erfasst werden.

In diesem Fall sind Sie besser dran, wenn Sie Verhalten wie folgt abstrahieren:

public interface IFoodEater
{
    void Eat(IFood food);
}

public class Animal : IFoodEater
{
    private IFoodProcessor _foodProcessor;
    public Animal(IFoodProcessor foodProcessor)
    {
        _foodProcessor = foodProcessor;
    }

    public void Eat(IFood food)
    {
        _foodProcessor.Process(food);
    }
}

Oder implementieren Sie ein Besuchermuster, bei dem FoodProcessorversucht wird, kompatible Tiere zu füttern ( ICarnivore : IFoodEater, MeatProcessingVisitor). Der Gedanke an lebende Tiere kann Sie daran hindern, daran zu denken, ihre Verdauungsspur herauszureißen und durch eine generische zu ersetzen, aber Sie tun ihnen wirklich einen Gefallen.

Dies macht Ihr Tier zu einer Basisklasse, und noch besser zu einer, die Sie nie wieder ändern müssen, wenn Sie eine neue Art von Futter und einen entsprechenden Verarbeiter hinzufügen, damit Sie sich auf die Dinge konzentrieren können, die diese bestimmte Tierklasse zu Ihnen machen Arbeiten an so einzigartig.

Also ja, Basisklassen haben ihren Platz, die Faustregel gilt (oft, nicht immer, da es sich um eine Faustregel handelt), aber suchen Sie weiter nach Verhalten, das Sie isolieren können.

CodeCaster
quelle
1
Der IFoodEater ist irgendwie redundant. Was erwarten Sie sonst noch, um essen zu können? Ja, Tiere als Beispiele sind schreckliche Dinge.
Euphoric
1
Was ist mit fleischfressenden Pflanzen? :) Im Ernst, ein Hauptproblem bei der Nichtverwendung von Schnittstellen in diesem Fall ist, dass Sie das Konzept des Essens mit Tieren eng miteinander verbunden haben und es viel mehr Arbeit ist, neue Abstraktionen einzuführen, für die die Tier-Basisklasse nicht geeignet ist.
Julia Hayward
1
Ich glaube, "Essen" ist hier die falsche Idee: abstrakter werden. Wie wäre es mit einer IConsumerSchnittstelle. Fleischfresser können Fleisch konsumieren, Pflanzenfresser Pflanzen und Pflanzen Sonnenlicht und Wasser.
2

Eine Schnittstelle ist wirklich ein Vertrag. Es gibt keine Funktionalität. Die Implementierung muss vom Implementierer durchgeführt werden. Es gibt keine Wahl, da man den Vertrag umsetzen muss.

Abstrakte Klassen geben Implementierern die Wahl. Man kann sich für eine Methode entscheiden, die jeder implementieren muss, eine Methode mit Basisfunktionalität bereitstellen, die überschrieben werden kann, oder eine Basisfunktionalität bereitstellen, die alle Erben verwenden können.

Zum Beispiel:

public abstract class Person
    {
        /// <summary>
        /// Inheritors must implement a hello
        /// </summary>
        /// <returns>Hello</returns>
        public abstract string SayHello();
        /// <summary>
        /// Inheritors can use base functionality, or override
        /// </summary>
        /// <returns></returns>
        public virtual string SayGoodBye()
        {
            return "Good Bye";
        }
        /// <summary>
        /// Base Functionality that is inherited 
        /// </summary>
        public void DoSomething()
        {
            //Do Something Here
        }
    }

Ich würde sagen, Ihre Faustregel könnte auch von einer Basisklasse behandelt werden. Insbesondere da viele Säugetiere wahrscheinlich dasselbe "essen", ist die Verwendung einer Basisklasse möglicherweise besser als eine Schnittstelle, da eine virtuelle Essmethode implementiert werden könnte, die die meisten Erben verwenden könnten, und dann die Ausnahmefälle überschreiben könnten.

Wenn nun die Definition von "Essen" von Tier zu Tier stark variiert, wäre vielleicht und Schnittstelle besser. Dies ist manchmal eine Grauzone und hängt von der individuellen Situation ab.

Jon Raynor
quelle
1

Nein.

Bei Schnittstellen geht es darum, wie die Methoden implementiert werden. Wenn Sie Ihre Entscheidung, wann Sie die Schnittstelle verwenden möchten, darauf stützen, wie die Methoden implementiert werden, dann machen Sie etwas falsch.

Für mich liegt der Unterschied zwischen Schnittstelle und Basisklasse darin, wo die Anforderungen stehen. Sie erstellen eine Schnittstelle, wenn für einen Code eine Klasse mit einer bestimmten API und einem impliziten Verhalten erforderlich ist, das sich aus dieser API ergibt. Sie können keine Schnittstelle ohne Code erstellen, der sie tatsächlich aufruft.

Auf der anderen Seite sind Basisklassen normalerweise als Möglichkeit zur Wiederverwendung von Code gedacht, sodass Sie sich selten mit Code beschäftigen, der diese Basisklasse aufruft, und nur die Suche nach Objekten berücksichtigen, zu denen diese Basisklasse gehört.

Euphorisch
quelle
Warten Sie, warum eat()bei jedem Tier neu implementieren ? Wir können es mammalumsetzen, nicht wahr? Ich verstehe jedoch Ihren Standpunkt zu den Anforderungen.
Sbichenko
@exizt: Entschuldigung, ich habe deine Frage falsch gelesen.
Euphoric
1

Dies ist keine gute Faustregel, denn wenn Sie unterschiedliche Implementierungen wünschen, können Sie immer eine abstrakte Basisklasse mit einer abstrakten Methode haben. Jeder Erbe hat garantiert diese Methode, aber jeder definiert seine eigene Implementierung. Technisch gesehen könnte man eine abstrakte Klasse mit nur einer Reihe abstrakter Methoden haben, und das wäre im Grunde dasselbe wie eine Schnittstelle.

Basisklassen sind nützlich, wenn Sie eine tatsächliche Implementierung eines Status oder Verhaltens wünschen, das für mehrere Klassen verwendet werden kann. Wenn Sie mehrere Klassen mit identischen Feldern und Methoden mit identischen Implementierungen haben, können Sie diese wahrscheinlich in eine Basisklasse umgestalten. Sie müssen nur vorsichtig sein, wie Sie erben. Jedes vorhandene Feld oder jede vorhandene Methode in der Basisklasse muss im Erben sinnvoll sein, insbesondere wenn es als Basisklasse umgewandelt wird.

Schnittstellen sind nützlich, wenn Sie auf die gleiche Weise mit einer Reihe von Klassen interagieren müssen, die tatsächliche Implementierung jedoch nicht vorhersagen oder sich nicht darum kümmern können. Nehmen Sie zum Beispiel so etwas wie eine Sammlungsschnittstelle. Herr kennt nur alle unzähligen Möglichkeiten, wie sich jemand entscheiden könnte, eine Sammlung von Gegenständen zu implementieren. Verknüpfte Liste? Anordnungsliste? Stapel? Warteschlange? Diese SearchAndReplace-Methode kümmert sich nicht darum, sie muss nur etwas übergeben werden, das Add, Remove und GetEnumerator aufrufen kann.

user1100269
quelle
1

Ich beobachte die Antworten auf diese Frage selbst, um zu sehen, was andere Leute zu sagen haben, aber ich werde drei Dinge sagen, über die ich gerne nachdenke:

  1. Die Leute setzen viel zu viel Vertrauen in Schnittstellen und zu wenig Vertrauen in Klassen. Schnittstellen sind im Wesentlichen stark verwässerte Basisklassen, und es gibt Fälle, in denen die Verwendung von etwas Leichtem zu übermäßigem Code für Boilerplates führen kann.

  2. Es ist überhaupt nichts Falsches daran, eine Methode zu überschreiben, sie aufzurufen super.method()und den Rest ihres Körpers nur Dinge tun zu lassen, die die Basisklasse nicht stören. Dies verstößt nicht gegen das Liskov-Substitutionsprinzip .

  3. Als Faustregel gilt, dass es eine schlechte Idee ist, so starr und dogmatisch in der Softwareentwicklung zu sein, auch wenn etwas im Allgemeinen eine bewährte Methode ist.

Also würde ich das Gesagte mit einem Körnchen Salz nehmen.

Panzerkrise
quelle
5
People put way too much faith into interfaces, and too little faith into classes.Komisch. Ich neige dazu zu denken, dass das Gegenteil der Fall ist.
Simon Bergot
1
"Dies verstößt nicht gegen das Liskov-Substitutionsprinzip." Aber es ist sehr nahe daran, eine Verletzung von LSP zu werden. Lieber auf Nummer sicher gehen.
Euphoric
1

Ich möchte diesbezüglich einen lingualen und semantischen Ansatz verfolgen. Eine Schnittstelle ist nicht da für DRY. Obwohl es neben einer abstrakten Klasse, die es implementiert, als Mechanismus für die Zentralisierung verwendet werden kann, ist es nicht für DRY gedacht.

Eine Schnittstelle ist ein Vertrag.

IAnimalSchnittstelle ist ein Alles-oder-Nichts-Vertrag zwischen Alligator, Katze, Hund und der Vorstellung eines Tieres. Im Wesentlichen heißt es: "Wenn Sie ein Tier sein wollen, müssen Sie entweder alle diese Eigenschaften und Merkmale haben oder Sie sind kein Tier mehr."

Die Vererbung auf der anderen Seite ist ein Mechanismus zur Wiederverwendung. In diesem Fall kann Ihnen eine gute semantische Argumentation bei der Entscheidung helfen, welche Methode wohin gehen soll. Während zum Beispiel alle Tiere schlafen und gleich schlafen, werde ich keine Basisklasse dafür verwenden. Ich habe die Sleep()Methode zuerst IAnimalals Betonung (semantisch) für das, was es braucht, um ein Tier zu sein, in die Benutzeroberfläche eingefügt. Dann verwende ich eine abstrakte Basisklasse für Katze, Hund und Alligator als Programmiertechnik, um mich nicht zu wiederholen.

Saeed Neamati
quelle
0

Ich bin mir nicht sicher, ob dies die beste Faustregel ist. Sie können eine abstractMethode in die Basisklasse einfügen und von jeder Klasse implementieren lassen. Da jeder mammalessen muss, wäre es sinnvoll, das zu tun. Dann mammalkann jeder seine eine eatMethode anwenden. Normalerweise verwende ich nur Schnittstellen für die Kommunikation zwischen Objekten oder als Marker, um eine Klasse als etwas zu markieren. Ich denke, dass die Überbeanspruchung von Schnittstellen zu schlampigem Code führt.

Ich stimme auch @Panzercrisis zu, dass eine Faustregel nur eine allgemeine Richtlinie sein sollte. Nicht etwas, das in Stein gemeißelt sein sollte. Es wird zweifellos Zeiten geben, in denen es einfach nicht funktioniert.

Jason Crosby
quelle
-1

Schnittstellen sind Typen. Sie bieten keine Implementierung. Sie definieren einfach den Vertrag für die Bearbeitung dieses Typs. Dieser Vertrag muss von jeder Klasse erfüllt werden, die die Schnittstelle implementiert.

Die Verwendung von Schnittstellen und abstrakten / Basisklassen sollte durch die Entwurfsanforderungen bestimmt werden.

Eine einzelne Klasse benötigt keine Schnittstelle.

Wenn zwei Klassen innerhalb einer Funktion austauschbar sind, sollten sie eine gemeinsame Schnittstelle implementieren. Jede Funktionalität, die identisch implementiert ist, könnte dann in eine abstrakte Klasse umgestaltet werden, die die Schnittstelle implementiert. Die Schnittstelle könnte zu diesem Zeitpunkt als redundant angesehen werden, wenn keine Unterscheidungsfunktion vorhanden ist. Aber was ist dann der Unterschied zwischen den beiden Klassen?

Die Verwendung abstrakter Klassen als Typen ist im Allgemeinen unübersichtlich, wenn mehr Funktionen hinzugefügt und eine Hierarchie erweitert wird.

Bevorzugen Sie die Komposition gegenüber der Vererbung - all dies verschwindet.

user108620
quelle