840 Stimmen

Schnittstelle vs. Basisklasse

Wann sollte ich eine Schnittstelle und wann eine Basisklasse verwenden?

Sollte es immer eine Schnittstelle sein, wenn ich keine Basisimplementierung der Methoden definieren möchte?

Wenn ich eine Hunde- und Katzenklasse habe. Warum sollte ich IPet anstelle von PetBase implementieren wollen? Ich kann verstehen, mit Schnittstellen für ISheds oder IBarks (IMakesNoise?), weil diese auf ein Haustier von Haustier Basis platziert werden können, aber ich verstehe nicht, welche für eine allgemeine Pet verwenden.

11 Stimmen

Nur ein Punkt, den Sie meiner Meinung nach berücksichtigen sollten - Schnittstellen können verschiedene Einschränkungen mit sich bringen, derer Sie sich möglicherweise erst in sehr späten Phasen bewusst sind. Zum Beispiel mit .NET können Sie nicht serialisieren eine Schnittstelle Mitglied Variable, so dass, wenn Sie eine Klasse Zoo und ein Mitglied Variable Array von IAnimals haben Sie nicht in der Lage, Zoo serialisieren (und das bedeutet, schreiben WebServices oder andere Dinge, die eine Serialisierung wäre ein Schmerz).

2 Stimmen

Diese Frage könnte helfen, das Konzept der Schnittstellen zu verstehen. stackoverflow.com/q/8531292/1055241

0 Stimmen

Ich bin nur neugierig. Ich traf in der CLR über C# den folgenden Auszug: I tend to prefer using the interface technique over the base type technique because the base type technique doesn’t allow the developer to choose the base type that works best in a particular situation. . Ich kann nicht begreifen, was in dem Auszug gemeint ist. Wir können einige Basistypen erstellen und für jeden von ihnen einen abgeleiteten Typ erstellen, so dass ein Entwickler einen Basistyp auswählen kann. Könnte mir bitte jemand erklären, was ich übersehe? Ich glaube, es kann ein Teil dieser Frage sein. Oder sollte ich eine andere Frage zu dem spezifischen Auszug stellen?

12voto

Ich empfehle, wann immer möglich, Komposition statt Vererbung zu verwenden. Verwenden Sie Schnittstellen, aber verwenden Sie Mitgliedsobjekte für die Basisimplementierung. Auf diese Weise können Sie eine Fabrik definieren, die Ihre Objekte so konstruiert, dass sie sich auf eine bestimmte Weise verhalten. Wenn Sie das Verhalten ändern möchten, erstellen Sie eine neue Fabrikmethode (oder eine abstrakte Fabrik), die verschiedene Arten von Unterobjekten erzeugt.

In einigen Fällen kann es vorkommen, dass Ihre primären Objekte überhaupt keine Schnittstellen benötigen, wenn das gesamte veränderliche Verhalten in Hilfsobjekten definiert ist.

Anstelle von IPet oder PetBase könnte man also ein Pet mit einem IFurBehavior-Parameter erhalten. Der Parameter IFurBehavior wird von der Methode CreateDog() der PetFactory gesetzt. Es ist dieser Parameter, der für die Methode shed() aufgerufen wird.

Wenn Sie dies tun, werden Sie feststellen, dass Ihr Code viel flexibler ist und die meisten Ihrer einfachen Objekte sich mit sehr grundlegenden systemweiten Verhaltensweisen befassen.

Ich empfehle dieses Muster sogar bei Sprachen mit Mehrfachvererbung.

12voto

Sijin Punkte 4490

Denken Sie auch daran, sich nicht in OO zu verlieren ( siehe Blog ) und modellieren Sie Objekte immer auf der Grundlage des erforderlichen Verhaltens. Wenn Sie eine Anwendung entwerfen würden, bei der das einzige erforderliche Verhalten ein allgemeiner Name und eine Art für ein Tier ist, dann bräuchten Sie nur eine Klasse Animal mit einer Eigenschaft für den Namen, anstatt Millionen von Klassen für jedes mögliche Tier der Welt.

11voto

Jason Roell Punkte 6403

Quelle : http://jasonroell.com/2014/12/09/interfaces-vs-abstract-classes-what-should-you-use/

C# ist eine wunderbare Sprache, die in den letzten 14 Jahren gereift ist und sich weiterentwickelt hat. Das ist großartig für uns Entwickler, denn eine ausgereifte Sprache bietet uns eine Fülle von Sprachfunktionen, die uns zur Verfügung stehen.

Doch mit viel Macht geht auch viel Verantwortung einher. Einige dieser Funktionen können missbraucht werden, oder manchmal ist es schwer zu verstehen, warum man eine Funktion einer anderen vorziehen sollte. Im Laufe der Jahre habe ich gesehen, dass viele Entwickler mit der Frage ringen, wann sie eine Schnittstelle oder eine abstrakte Klasse verwenden sollten. Beide haben ihre Vor- und Nachteile und die richtige Zeit und den richtigen Ort für ihre Verwendung. Aber wie soll man sich entscheiden???

Beide ermöglichen die Wiederverwendung gemeinsamer Funktionen zwischen Typen. Der offensichtlichste Unterschied besteht darin, dass Schnittstellen keine Implementierung ihrer Funktionalität bieten, während abstrakte Klassen die Möglichkeit bieten, ein "Basis"- oder "Standard"-Verhalten zu implementieren und dann die Möglichkeit haben, dieses Standardverhalten mit den abgeleiteten Typen der Klasse zu "überschreiben", falls erforderlich.

Das ist alles schön und gut und sorgt für eine großartige Wiederverwendung von Code und hält sich an das DRY-Prinzip (Don't Repeat Yourself) der Softwareentwicklung. Abstrakte Klassen sind hervorragend geeignet, wenn Sie eine "ist ein"-Beziehung haben.

Zum Beispiel: Ein Golden Retriever ist eine" Art von Hund. Das gilt auch für einen Pudel. Sie können beide bellen, wie alle Hunde. Sie könnten jedoch angeben, dass der Pudelpark sich deutlich vom "Standard"-Hundebellen unterscheidet. Daher könnte es für Sie sinnvoll sein, etwas wie folgt zu implementieren:

public abstract class Dog
{
      public virtual void Bark()
      {
        Console.WriteLine("Base Class implementation of Bark");
      }
}

public class GoldenRetriever : Dog
{
   // the Bark method is inherited from the Dog class
}

public class Poodle : Dog
{
  // here we are overriding the base functionality of Bark with our new implementation
  // specific to the Poodle class
  public override void Bark()
  {
     Console.WriteLine("Poodle's implementation of Bark");
  }
}

// Add a list of dogs to a collection and call the bark method.

void Main()
{
    var poodle = new Poodle();
    var goldenRetriever = new GoldenRetriever();

    var dogs = new List<Dog>();
    dogs.Add(poodle);
    dogs.Add(goldenRetriever);

    foreach (var dog in dogs)
    {
       dog.Bark();
    }
}

// Output will be:
// Poodle's implementation of Bark
// Base Class implementation of Bark

// 

Wie Sie sehen können, wäre dies ein großartiger Weg, um Ihren Code DRY zu halten und ermöglichen für die Basisklasse Implementierung aufgerufen werden, wenn einer der Typen kann nur auf die Standard-Bark anstelle eines Sonderfalls Implementierung verlassen. Die Klassen wie GoldenRetriever, Boxer, Lab könnten alle das "Standard" (Basisklasse) Bark kostenlos erben, nur weil sie die abstrakte Klasse Dog implementieren.

Aber ich bin sicher, das wussten Sie schon.

Sie sind hier, weil Sie verstehen wollen, warum Sie eine Schnittstelle einer abstrakten Klasse vorziehen sollten oder umgekehrt. Ein Grund, warum Sie eine Schnittstelle einer abstrakten Klasse vorziehen sollten, ist, wenn Sie keine Standardimplementierung haben oder diese verhindern wollen. Dies liegt in der Regel daran, dass die Typen, die die Schnittstelle implementieren, nicht in einer "ist ein"-Beziehung zueinander stehen. Eigentlich müssen sie überhaupt nicht miteinander verwandt sein, außer der Tatsache, dass jeder Typ "in der Lage" ist oder "die Fähigkeit" hat, etwas zu tun oder etwas zu haben.

Was zum Teufel bedeutet das nun? Nun, zum Beispiel: Ein Mensch ist keine Ente und eine Ente ist kein Mensch. Ziemlich offensichtlich. Aber sowohl eine Ente als auch ein Mensch haben "die Fähigkeit" zu schwimmen (vorausgesetzt, der Mensch hat seinen Schwimmunterricht in der ersten Klasse bestanden :) ). Da eine Ente kein Mensch ist und umgekehrt, handelt es sich nicht um eine "ist ein"-Beziehung, sondern um eine "kann"-Beziehung, und wir können eine Schnittstelle verwenden, um dies zu veranschaulichen:

// Create ISwimable interface
public interface ISwimable
{
      public void Swim();
}

// Have Human implement ISwimable Interface
public class Human : ISwimable

     public void Swim()
     {
        //Human's implementation of Swim
        Console.WriteLine("I'm a human swimming!");
     }

// Have Duck implement ISwimable interface
public class Duck: ISwimable
{
     public void Swim()
     {
          // Duck's implementation of Swim
          Console.WriteLine("Quack! Quack! I'm a Duck swimming!")
     }
}

//Now they can both be used in places where you just need an object that has the ability "to swim"

public void ShowHowYouSwim(ISwimable somethingThatCanSwim)
{
     somethingThatCanSwim.Swim();
}

public void Main()
{
      var human = new Human();
      var duck = new Duck();

      var listOfThingsThatCanSwim = new List<ISwimable>();

      listOfThingsThatCanSwim.Add(duck);
      listOfThingsThatCanSwim.Add(human);

      foreach (var something in listOfThingsThatCanSwim)
      {
           ShowHowYouSwim(something);
      }
}

 // So at runtime the correct implementation of something.Swim() will be called
 // Output:
 // Quack! Quack! I'm a Duck swimming!
 // I'm a human swimming!

Die Verwendung von Schnittstellen wie im obigen Code ermöglicht es Ihnen, ein Objekt an eine Methode zu übergeben, die etwas tun "kann". Der Code kümmert sich nicht darum, wie er es tut Er weiß nur, dass er die Swim-Methode für dieses Objekt aufrufen kann und dass das Objekt weiß, welches Verhalten es zur Laufzeit aufgrund seines Typs annehmen wird.

Auch dies trägt dazu bei, dass Ihr Code trocken bleibt, so dass Sie nicht mehrere Methoden schreiben müssen, die das Objekt aufrufen, um dieselbe Kernfunktion auszuführen (ShowHowHumanSwims(human), ShowHowDuckSwims(duck), usw.).

Die Verwendung einer Schnittstelle ermöglicht es den aufrufenden Methoden, sich nicht darum zu kümmern, welcher Typ welcher ist oder wie das Verhalten implementiert wird. Sie weiß nur, dass angesichts der Schnittstelle jedes Objekt die Swim-Methode implementiert haben muss, so dass es sicher ist, sie in seinem eigenen Code aufzurufen und zuzulassen, dass das Verhalten der Swim-Methode innerhalb seiner eigenen Klasse behandelt wird.

Zusammenfassung:

Meine wichtigste Faustregel lautet also: Verwenden Sie eine abstrakte Klasse, wenn Sie eine "Standard"-Funktionalität für eine Klassenhierarchie implementieren möchten oder/und wenn die Klassen oder Typen, mit denen Sie arbeiten, eine "ist ein"-Beziehung aufweisen (z. B. Pudel "ist ein" Typ von Hund).

Andererseits verwenden Sie eine Schnittstelle, wenn Sie keine "ist ein"-Beziehung haben, sondern Typen, die "die Fähigkeit" haben, etwas zu tun oder zu haben (z. B. Ente "ist nicht" ein Mensch. Ente und Mensch teilen sich jedoch die "Fähigkeit" zu schwimmen).

Ein weiterer Unterschied zwischen abstrakten Klassen und Schnittstellen besteht darin, dass eine Klasse eine oder mehrere Schnittstellen implementieren kann, aber eine Klasse kann nur von EINER abstrakten Klasse (oder einer beliebigen Klasse) erben. Ja, man kann Klassen verschachteln und eine Vererbungshierarchie haben (was viele Programme tun und haben sollten), aber man kann nicht zwei Klassen in einer abgeleiteten Klassendefinition erben (diese Regel gilt für C#. In einigen anderen Sprachen ist dies möglich, normalerweise aber nur, weil es in diesen Sprachen keine Schnittstellen gibt).

Denken Sie auch daran, bei der Verwendung von Schnittstellen das Prinzip der Schnittstellentrennung (ISP) einzuhalten. Das ISP besagt, dass kein Client gezwungen werden sollte, von Methoden abhängig zu sein, die er nicht verwendet. Aus diesem Grund sollten sich Schnittstellen auf bestimmte Aufgaben konzentrieren und in der Regel sehr klein sein (z. B. IDisposable, IComparable).

Ein weiterer Tipp ist die Verwendung von Schnittstellen, wenn Sie kleine, prägnante Funktionseinheiten entwickeln. Wenn Sie große Funktionseinheiten entwickeln, verwenden Sie eine abstrakte Klasse.

Ich hoffe, das klärt die Sache für einige Leute auf!

Wenn Ihnen noch bessere Beispiele einfallen oder Sie auf etwas hinweisen möchten, schreiben Sie es bitte in die Kommentare unten!

10voto

Akhil Jain Punkte 13218

Ich habe eine grobe Faustregel

Funktionsweise: wahrscheinlich in allen Teilen unterschiedlich sein: Schnittstelle.

Die Daten und die Funktionalität werden zum Teil gleich, zum Teil unterschiedlich sein: abstrakte Klasse.

Daten und Funktionen, die tatsächlich funktionieren, wenn auch nur mit geringfügigen Änderungen erweitert: gewöhnliche (konkrete) Klasse

Daten und Funktionalität, keine Änderungen geplant: gewöhnliche (konkrete) Klasse mit finalem Modifikator.

Daten und vielleicht Funktionalität: schreibgeschützt: Enum-Mitglieder.

Dies ist nur sehr grob und überhaupt nicht streng definiert, aber es gibt ein Spektrum von Schnittstellen, bei denen alles geändert werden soll, bis hin zu Enums, bei denen alles wie eine schreibgeschützte Datei festgelegt ist.

7voto

Jarrett Meyer Punkte 18916

Die Schnittstellen sollten klein sein. Wirklich klein. Wenn Sie Ihre Objekte wirklich aufschlüsseln, dann werden Ihre Schnittstellen wahrscheinlich nur ein paar sehr spezifische Methoden und Eigenschaften enthalten.

Abstrakte Klassen sind Abkürzungen. Gibt es Dinge, die alle Derivate von PetBase gemeinsam haben, die Sie einmal programmieren können und damit fertig sind? Wenn ja, dann ist es Zeit für eine abstrakte Klasse.

Abstrakte Klassen sind ebenfalls einschränkend. Sie bieten zwar eine großartige Abkürzung zur Erzeugung von untergeordneten Objekten, aber jedes Objekt kann nur eine abstrakte Klasse implementieren. Oftmals empfinde ich dies als eine Einschränkung von abstrakten Klassen, weshalb ich viele Schnittstellen verwende.

Abstrakte Klassen können mehrere Schnittstellen enthalten. Ihre abstrakte Klasse PetBase kann IPet (Haustiere haben Besitzer) und IDigestion (Haustiere essen, oder sollten es zumindest) implementieren. Allerdings wird PetBase wahrscheinlich nicht IMammal implementieren, da nicht alle Haustiere Säugetiere sind und nicht alle Säugetiere Haustiere sind. Du kannst eine MammalPetBase hinzufügen, die PetBase erweitert und IMammal hinzufügt. FishBase könnte PetBase haben und IFish hinzufügen. IFish würde ISwim und IUnderwaterBreather als Schnittstellen haben.

Ja, mein Beispiel ist für ein einfaches Beispiel reichlich überkompliziert, aber das ist ja gerade das Tolle daran, wie Schnittstellen und abstrakte Klassen zusammenarbeiten.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X