Mehrfachvererbung in C #

212

Da die Mehrfachvererbung schlecht ist (was die Quelle komplizierter macht), liefert C # ein solches Muster nicht direkt. Aber manchmal wäre es hilfreich, diese Fähigkeit zu haben.

Zum Beispiel kann ich das fehlende Mehrfachvererbungsmuster mithilfe von Schnittstellen und drei Klassen wie diesen implementieren:

public interface IFirst { void FirstMethod(); }
public interface ISecond { void SecondMethod(); }

public class First:IFirst 
{ 
    public void FirstMethod() { Console.WriteLine("First"); } 
}

public class Second:ISecond 
{ 
    public void SecondMethod() { Console.WriteLine("Second"); } 
}

public class FirstAndSecond: IFirst, ISecond
{
    First first = new First();
    Second second = new Second();
    public void FirstMethod() { first.FirstMethod(); }
    public void SecondMethod() { second.SecondMethod(); }
}

Jedes Mal, wenn ich einer der Schnittstellen eine Methode hinzufüge, muss ich auch die Klasse FirstAndSecond ändern .

Gibt es eine Möglichkeit, mehrere vorhandene Klassen in eine neue Klasse einzufügen, wie dies in C ++ möglich ist?

Vielleicht gibt es eine Lösung mit einer Art Codegenerierung?

Oder es könnte so aussehen (imaginäre c # -Syntax):

public class FirstAndSecond: IFirst from First, ISecond from Second
{ }

Damit die Klasse FirstAndSecond nicht aktualisiert werden muss, wenn ich eine der Schnittstellen ändere.


BEARBEITEN

Vielleicht wäre es besser, ein praktisches Beispiel zu betrachten:

Sie haben eine vorhandene Klasse (z. B. einen textbasierten TCP-Client, der auf ITextTcpClient basiert), die Sie bereits an verschiedenen Stellen in Ihrem Projekt verwenden. Jetzt müssen Sie eine Komponente Ihrer Klasse erstellen, die für Windows Forms-Entwickler leicht zugänglich ist.

Soweit ich weiß, haben Sie derzeit zwei Möglichkeiten, dies zu tun:

  1. Schreiben Sie eine neue Klasse, die von Komponenten geerbt wird und die Schnittstelle der TextTcpClient-Klasse mithilfe einer Instanz der Klasse selbst implementiert, wie in FirstAndSecond gezeigt.

  2. Schreiben Sie eine neue Klasse, die von TextTcpClient erbt und IComponent irgendwie implementiert (haben dies noch nicht ausprobiert).

In beiden Fällen müssen Sie pro Methode und nicht pro Klasse arbeiten. Da Sie wissen, dass wir alle Methoden von TextTcpClient und Component benötigen, ist es die einfachste Lösung, diese beiden Methoden in einer Klasse zu kombinieren.

Um Konflikte zu vermeiden, kann dies durch Codegenerierung erfolgen, bei der das Ergebnis später geändert werden kann, aber das Eingeben von Hand ist ein reiner Schmerz im Arsch.

Martin
quelle
Wie ist es weniger kompliziert, wenn dies nicht einfach eine Mehrfachvererbung in Verkleidung ist?
Harpo
Wenn Sie über die neuen Erweiterungsmethoden in 3.5 und deren Funktionsweise (Generierung statischer Mitgliederaufrufe) nachdenken, ist dies möglicherweise eine der nächsten .NET-Sprachentwicklungen.
Larry
Manchmal frage ich mich, warum die Leute nicht einfach ... Klasse A: Klasse B: Klasse C tun?
Chibueze Opata
@ NazarMerza: Link hat sich geändert. Jetzt: Das Problem mit der Mehrfachvererbung .
Craig McQueen
9
Lass dich nicht von Propaganda täuschen. Ihr Beispiel zeigt, dass Mehrfachvererbung nützlich ist und Schnittstellen nur eine Problemumgehung für das Fehlen sind
Kemal Erdogan

Antworten:

125

Da die Mehrfachvererbung schlecht ist (was die Quelle komplizierter macht), liefert C # ein solches Muster nicht direkt. Aber manchmal wäre es hilfreich, diese Fähigkeit zu haben.

C # und die .net CLR haben MI nicht implementiert, weil sie noch nicht abgeschlossen haben, wie es zwischen C #, VB.net und den anderen Sprachen zusammenarbeiten würde, nicht weil "es die Quelle komplexer machen würde".

MI ist ein nützliches Konzept. Die nicht beantworteten Fragen lauten wie folgt: - "Was machen Sie, wenn Sie mehrere gemeinsame Basisklassen in den verschiedenen Oberklassen haben?

Perl ist die einzige Sprache, mit der ich jemals gearbeitet habe, in der MI funktioniert und gut funktioniert. .Net wird es vielleicht eines Tages einführen, aber noch nicht, die CLR unterstützt MI bereits, aber wie gesagt, es gibt noch keine Sprachkonstrukte dafür.

Bis dahin stecken Sie stattdessen mit Proxy-Objekten und mehreren Schnittstellen fest :(

Ian Norton
quelle
39
Die CLR unterstützt nicht die Vererbung mehrerer Implementierungen, sondern nur die Vererbung mehrerer Schnittstellen (die auch in C # unterstützt wird).
Jordão
4
@ Jordão: Der Vollständigkeit halber: Compiler können MI für ihre Typen in der CLR erstellen. Es hat seine Vorbehalte, es ist zum Beispiel nicht CLS-konform. Weitere Informationen finden Sie in diesem (2004) Artikel blogs.msdn.com/b/csharpfaq/archive/2004/03/07/…
dvdvorle
2
@ MrHappy: Sehr interessanter Artikel. Ich habe tatsächlich eine Art der Trait Composition für C # untersucht.
Jordão
10
@MandeepJanjua Ich habe so etwas nicht behauptet, ich sagte 'kann es gut einführen'. Es bleibt die Tatsache, dass der ECMA-Standard CLR die IL-Maschinerie für Mehrfachvererbung bereitstellt, nur dass nichts sie vollständig nutzt.
IanNorton
4
Zu Ihrer Information: Mehrfachvererbung ist nicht schlecht und macht Code nicht so kompliziert. Ich dachte nur, ich würde es erwähnen.
Dmitri Nesteruk
214

Verwenden Sie nur die Komposition, anstatt zu versuchen, die Mehrfachvererbung zu simulieren. Sie können Schnittstellen verwenden, um zu definieren, aus welchen Klassen die Komposition besteht, z. B.: ISteerableImpliziert eine Eigenschaft vom Typ SteeringWheel, IBrakableimpliziert eine Eigenschaft vom Typ BrakePedalusw.

Sobald Sie das getan haben, können Sie die Verwendung Erweiterungsmethoden Features auf diesen impliziten Eigenschaften zu C # 3.0 weiter zu vereinfachen calling Methoden hinzugefügt, zum Beispiel:

public interface ISteerable { SteeringWheel wheel { get; set; } }

public interface IBrakable { BrakePedal brake { get; set; } }

public class Vehicle : ISteerable, IBrakable
{
    public SteeringWheel wheel { get; set; }

    public BrakePedal brake { get; set; }

    public Vehicle() { wheel = new SteeringWheel(); brake = new BrakePedal(); }
}

public static class SteeringExtensions
{
    public static void SteerLeft(this ISteerable vehicle)
    {
        vehicle.wheel.SteerLeft();
    }
}

public static class BrakeExtensions
{
    public static void Stop(this IBrakable vehicle)
    {
        vehicle.brake.ApplyUntilStop();
    }
}


public class Main
{
    Vehicle myCar = new Vehicle();

    public void main()
    {
        myCar.SteerLeft();
        myCar.Stop();
    }
}
Chris Wenham
quelle
13
Das ist jedoch der Punkt - eine Idee wie diese würde die Komposition erleichtern.
Jon Skeet
9
Ja, aber es gibt Anwendungsfälle, in denen Sie die Methoden wirklich als Teil des Hauptobjekts benötigen
David Pierre
9
Leider ist auf Daten von Mitgliedsvariablen in Erweiterungsmethoden nicht zugegriffen werden können, sodass Sie sie als intern oder (ug) öffentlich verfügbar machen müssen, obwohl ich denke, dass die vertragliche Zusammensetzung der beste Weg ist, um Mehrfachvererbung zu lösen.
cfeduke
4
Hervorragende Antwort! Prägnante, leicht verständliche, sehr nützliche Illustration. Danke dir!
AJ.
3
Wir möchten vielleicht überprüfen, ob myCardie Lenkung nach links beendet ist, bevor wir anrufen Stop. Es kann überrollen, wenn es bei zu hoher Geschwindigkeit Stopangewendet myCarwird. : D
Devraj Gadhavi
16

Ich habe einen C # -Post-Compiler erstellt , der Folgendes ermöglicht:

using NRoles;

public interface IFirst { void FirstMethod(); }
public interface ISecond { void SecondMethod(); }

public class RFirst : IFirst, Role {
  public void FirstMethod() { Console.WriteLine("First"); }
}

public class RSecond : ISecond, Role {
  public void SecondMethod() { Console.WriteLine("Second"); }
}

public class FirstAndSecond : Does<RFirst>, Does<RSecond> { }

Sie können den Post-Compiler als Visual Studio-Post-Build-Ereignis ausführen:

C: \ some_path \ nroles-v0.1.0-bin \ nutate.exe "$ (TargetPath)"

In derselben Baugruppe verwenden Sie es folgendermaßen:

var fas = new FirstAndSecond();
fas.As<RFirst>().FirstMethod();
fas.As<RSecond>().SecondMethod();

In einer anderen Baugruppe verwenden Sie es folgendermaßen:

var fas = new FirstAndSecond();
fas.FirstMethod();
fas.SecondMethod();
Jordão
quelle
6

Sie könnten eine abstrakte Basisklasse haben, die sowohl IFirst als auch ISecond implementiert und dann nur von dieser Basis erbt.

Joel Coehoorn
quelle
Dies ist wahrscheinlich die beste Lösung, aber nicht unbedingt die beste Idee: p
leppie
1
Müssten Sie die abstrakte Klasse nicht noch bearbeiten, wenn Sie den Schnittstellen Methoden hinzufügen?
Rik
Rik: Wie faul bist du, wenn du das nur einmal machen musst?
Leppie
3
@leppie - "Jedes Mal, wenn ich einer der Schnittstellen eine Methode hinzufüge, muss ich auch die Klasse FirstAndSecond ändern." Dieser Teil der ursprünglichen Frage wird von dieser Lösung nicht angesprochen, oder?
Rik
2
Sie müssten die abstrakte Klasse bearbeiten, aber Sie müssen KEINE anderen Klassen bearbeiten, die davon abhängen. Das Geld bleibt dort stehen, anstatt sich weiter auf die gesamte Klassensammlung auszudehnen.
Joel Coehoorn
3

MI ist NICHT schlecht, jeder, der es (ernsthaft) benutzt hat, LIEBT es und es kompliziert den Code NICHT! Zumindest nicht mehr als andere Konstrukte können den Code komplizieren. Schlechter Code ist schlechter Code, unabhängig davon, ob MI auf dem Bild ist oder nicht.

Wie auch immer, ich habe eine nette kleine Lösung für Multiple Inheritance, die ich teilen wollte. http://ra-ajax.org/lsp-liskov-substitution-principle-to-be-or-not-to-be.blog oder du kannst dem Link in meinem Sig folgen ... :)

Thomas Hansen
quelle
Ist es möglich, eine Mehrfachvererbung zu haben und dabei Upcasts und Downcasts identitätserhaltend zu haben? Die Lösungen, die ich für die Probleme der Mehrfachvererbung kenne, drehen sich um Casts, die nicht identitätserhaltend sind (wenn myFooes sich um einen Typ handelt Foo, der von Moound Goobeide erbt Boo, dann (Boo)(Moo)myFoound (Boo)(Goo)myFoonicht gleichwertig). Kennen Sie identitätserhaltende Ansätze?
Supercat
1
Sie können web.archive.org oder ähnliches verwenden, um dem Link zu folgen. Es stellt sich jedoch heraus, dass genau die in der ursprünglichen Frage hier angebotene Lösung ausführlicher besprochen wird.
MikeBeaton
2

In meiner eigenen Implementierung stellte ich fest, dass die Verwendung von Klassen / Schnittstellen für MI, obwohl "gute Form", eine massive Überkomplikation darstellt, da Sie die gesamte Mehrfachvererbung nur für wenige erforderliche Funktionsaufrufe einrichten müssen, und in meinem Fall. musste buchstäblich Dutzende Male redundant durchgeführt werden.

Stattdessen war es einfacher, statische "Funktionen, die Funktionen aufrufen, die Funktionen aufrufen" in verschiedenen modularen Varianten als eine Art OOP-Ersatz zu erstellen. Die Lösung, an der ich gearbeitet habe, war das "Zaubersystem" für ein Rollenspiel, bei dem Effekte stark aufgerufen werden müssen, um eine extreme Vielfalt von Zaubersprüchen zu erhalten, ohne den Code neu zu schreiben, ähnlich wie es das Beispiel zu zeigen scheint.

Die meisten Funktionen können jetzt statisch sein, da ich nicht unbedingt eine Instanz für die Zauberlogik benötige, während die Klassenvererbung im statischen Zustand nicht einmal virtuelle oder abstrakte Schlüsselwörter verwenden kann. Schnittstellen können sie überhaupt nicht verwenden.

Die Codierung scheint auf diese Weise IMO viel schneller und sauberer zu sein. Wenn Sie nur Funktionen ausführen und keine geerbten Eigenschaften benötigen , verwenden Sie Funktionen.

Hydrix
quelle
2

Mit C # 8 haben Sie jetzt praktisch mehrere Vererbungen über die Standardimplementierung von Schnittstellenmitgliedern:

interface ILogger
{
    void Log(LogLevel level, string message);
    void Log(Exception ex) => Log(LogLevel.Error, ex.ToString()); // New overload
}

class ConsoleLogger : ILogger
{
    public void Log(LogLevel level, string message) { ... }
    // Log(Exception) gets default implementation
}
Ludmil Tinkov
quelle
4
Ja, aber beachten Sie, dass Sie dies oben nicht tun können new ConsoleLogger().Log(someEception)- es wird einfach nicht funktionieren , Sie müssten Ihr Objekt explizit in a umwandeln ILogger, um die Standardschnittstellenmethode zu verwenden. Daher ist seine Nützlichkeit etwas eingeschränkt.
Dmitri Nesteruk
1

Wenn Sie mit der Einschränkung leben können, dass die Methoden von IFirst und ISecond nur mit dem Vertrag von IFirst und ISecond interagieren dürfen (wie in Ihrem Beispiel) ... können Sie mit Erweiterungsmethoden tun, was Sie verlangen. In der Praxis ist dies selten der Fall.

public interface IFirst {}
public interface ISecond {}

public class FirstAndSecond : IFirst, ISecond
{
}

public static MultipleInheritenceExtensions
{
  public static void First(this IFirst theFirst)
  {
    Console.WriteLine("First");
  }

  public static void Second(this ISecond theSecond)
  {
    Console.WriteLine("Second");
  }
}

///

public void Test()
{
  FirstAndSecond fas = new FirstAndSecond();
  fas.First();
  fas.Second();
}

Die Grundidee ist also, dass Sie die erforderliche Implementierung in den Schnittstellen definieren ... dieses erforderliche Material sollte die flexible Implementierung in den Erweiterungsmethoden unterstützen. Immer wenn Sie "Methoden zur Schnittstelle hinzufügen" müssen, fügen Sie stattdessen eine Erweiterungsmethode hinzu.

Amy B.
quelle
1

Ja, die Verwendung von Interface ist problematisch, da wir jedes Mal, wenn wir eine Methode in die Klasse einfügen, die Signatur in die Schnittstelle einfügen müssen. Was ist auch, wenn wir bereits eine Klasse mit einer Reihe von Methoden, aber ohne Schnittstelle dafür haben? Wir müssen die Schnittstelle für alle Klassen, von denen wir erben möchten, manuell erstellen. Und das Schlimmste ist, dass wir alle Methoden in den Schnittstellen in der untergeordneten Klasse implementieren müssen, wenn die untergeordnete Klasse von der Mehrfachschnittstelle erben soll.

Indem wir dem Fassadenentwurfsmuster folgen, können wir das Erben von mehreren Klassen mithilfe von Accessoren simulieren . Deklarieren Sie die Klassen als Eigenschaften mit {get; set;} innerhalb der Klasse, die geerbt werden müssen, und alle öffentlichen Eigenschaften und Methoden stammen von dieser Klasse, und instanziieren Sie im Konstruktor der untergeordneten Klasse die übergeordneten Klassen.

Beispielsweise:

 namespace OOP
 {
     class Program
     {
         static void Main(string[] args)
         {
             Child somechild = new Child();
             somechild.DoHomeWork();
             somechild.CheckingAround();
             Console.ReadLine();
         }
     }

     public class Father 
     {
         public Father() { }
         public void Work()
         {
             Console.WriteLine("working...");
         }
         public void Moonlight()
         {
             Console.WriteLine("moonlighting...");
         }
     }


     public class Mother 
     {
         public Mother() { }
         public void Cook()
         {
             Console.WriteLine("cooking...");
         }
         public void Clean()
         {
             Console.WriteLine("cleaning...");
         }
     }


     public class Child 
     {
         public Father MyFather { get; set; }
         public Mother MyMother { get; set; }

         public Child()
         {
             MyFather = new Father();
             MyMother = new Mother();
         }

         public void GoToSchool()
         {
             Console.WriteLine("go to school...");
         }
         public void DoHomeWork()
         {
             Console.WriteLine("doing homework...");
         }
         public void CheckingAround()
         {
             MyFather.Work();
             MyMother.Cook();
         }
     }


 }

Mit dieser Strukturklasse hat Child Zugriff auf alle Methoden und Eigenschaften der Klasse Vater und Mutter, simuliert die Mehrfachvererbung und erbt eine Instanz der übergeordneten Klassen. Nicht ganz das gleiche, aber es ist praktisch.

Yogi
quelle
2
Ich bin mit dem ersten Absatz nicht einverstanden. Sie fügen der Schnittstelle nur die Signaturen der gewünschten Methoden in JEDER Klasse hinzu. Sie können jedoch jeder Klasse so viele zusätzliche Methoden hinzufügen, wie Sie möchten. Außerdem gibt es eine Rechtsklick-Schnittstelle zum Extrahieren, die das Extrahieren von Schnittstellen vereinfacht. Schließlich ist Ihr Beispiel in keiner Weise Vererbung (mehrfach oder anderweitig), es ist jedoch ein großartiges Beispiel für Komposition. Leider wäre es sinnvoller, wenn Sie Schnittstellen verwendet hätten, um auch DI / IOC mithilfe der Konstruktor- / Eigenschaftsinjektion zu demonstrieren. Obwohl ich nicht abstimmen werde, denke ich nicht, dass es eine gute Antwort ist, sorry.
Francis Rodgers
1
Wenn ich ein Jahr später auf diesen Thread zurückblicke, stimme ich zu, dass Sie der Klasse so viele Methoden hinzufügen können, wie Sie möchten, ohne der Schnittstelle eine Signatur hinzuzufügen. Dies würde jedoch die Schnittstelle unvollständig machen. Außerdem konnte ich die Rechtsklick-Extrakt-Oberfläche in meiner IDE nicht finden, vielleicht fehlte mir etwas. Mein größeres Problem ist jedoch, dass die ererbenden Klassen diese Signatur implementieren müssen, wenn Sie eine Signatur in der Schnittstelle angeben. Ich denke, dies ist Doppelarbeit und könnte zu doppelten Codes führen.
Yogi
EXTRACT INTERFACE: Rechtsklick auf die Klassensignatur dann Schnittstelle extrahieren ... in VS2015 gleichen Prozess außer Sie einen Rechtsklick muss, dann wählen Quick Actions and Refactorings...dies ein wissen , ist , muss, wird es Ihnen viel Zeit sparen
Chef_Code
1

Wir scheinen alle auf diesem Weg den Schnittstellenpfad entlang zu gehen, aber die offensichtliche andere Möglichkeit besteht darin, das zu tun, was OOP tun soll, und Ihren Vererbungsbaum aufzubauen ... (ist das nicht alles, was Klassendesign ist? Über?)

class Program
{
    static void Main(string[] args)
    {
        human me = new human();
        me.legs = 2;
        me.lfType = "Human";
        me.name = "Paul";
        Console.WriteLine(me.name);
    }
}

public abstract class lifeform
{
    public string lfType { get; set; }
}

public abstract class mammal : lifeform 
{
    public int legs { get; set; }
}

public class human : mammal
{
    public string name { get; set; }
}

Diese Struktur bietet wiederverwendbare Codeblöcke und wie sollte OOP-Code geschrieben werden?

Wenn dieser spezielle Ansatz nicht ganz zur Rechnung passt, erstellen wir einfach neue Klassen basierend auf den erforderlichen Objekten ...

class Program
{
    static void Main(string[] args)
    {
        fish shark = new fish();
        shark.size = "large";
        shark.lfType = "Fish";
        shark.name = "Jaws";
        Console.WriteLine(shark.name);
        human me = new human();
        me.legs = 2;
        me.lfType = "Human";
        me.name = "Paul";
        Console.WriteLine(me.name);
    }
}

public abstract class lifeform
{
    public string lfType { get; set; }
}

public abstract class mammal : lifeform 
{
    public int legs { get; set; }
}

public class human : mammal
{
    public string name { get; set; }
}

public class aquatic : lifeform
{
    public string size { get; set; }
}

public class fish : aquatic
{
    public string name { get; set; }
}
Paul
quelle
0

Mehrfachvererbung ist eines der Dinge, die im Allgemeinen mehr Probleme verursachen als lösen. In C ++ passt es zu dem Muster, dass Sie genug Seil haben, um sich aufzuhängen, aber Java und C # haben sich entschieden, den sichereren Weg zu gehen, Ihnen nicht die Option zu geben. Das größte Problem ist, was zu tun ist, wenn Sie mehrere Klassen erben, die eine Methode mit derselben Signatur haben, die der Vererbte nicht implementiert. Welche Methode sollte die Klasse wählen? Oder sollte das nicht kompilieren? Es gibt im Allgemeinen eine andere Möglichkeit, die meisten Dinge zu implementieren, die nicht auf Mehrfachvererbung beruhen.

tloach
quelle
8
Bitte beurteilen Sie MI nicht nach C ++, das ist wie OOP nach PHP oder Automobile nach Pintos. Dieses Problem ist leicht lösbar: Wenn Sie in Eiffel von einer Klasse erben, müssen Sie auch angeben, welche Methoden Sie erben möchten, und Sie können sie umbenennen. Keine Unklarheiten und auch keine Überraschungen.
Jörg W Mittag
2
@mP: Nein, Eiffel bietet eine echte Vererbung mehrerer Implementierungen. Das Umbenennen bedeutet nicht, dass die Vererbungskette verloren geht, und es geht auch nicht um die Gießbarkeit von Klassen.
Abel
0

Wenn X von Y erbt, hat dies zwei etwas orthogonale Effekte:

  1. Y bietet Standardfunktionen für X, sodass der Code für X nur Dinge enthalten muss, die sich von Y unterscheiden.
  2. Fast überall dort, wo ein Y erwartet wird, kann stattdessen ein X verwendet werden.

Obwohl die Vererbung beide Funktionen bietet, ist es nicht schwer, sich Umstände vorzustellen, unter denen eine ohne die andere von Nutzen sein könnte. Keine mir bekannte .net-Sprache hat eine direkte Möglichkeit, die erste ohne die zweite zu implementieren, obwohl man eine solche Funktionalität erhalten könnte, indem man eine Basisklasse definiert, die niemals direkt verwendet wird, und eine oder mehrere Klassen hat, die direkt davon erben, ohne etwas hinzuzufügen neu (solche Klassen könnten ihren gesamten Code gemeinsam nutzen, wären aber nicht gegeneinander austauschbar). Jede CLR-kompatible Sprache ermöglicht jedoch die Verwendung von Schnittstellen, die das zweite Merkmal von Schnittstellen (Substituierbarkeit) ohne die erste (Wiederverwendung von Mitgliedern) bieten.

Superkatze
quelle
0

Ich weiß, ich weiß, obwohl es nicht erlaubt ist und so weiter, manchmal brauchst du es tatsächlich so für die:

class a {}
class b : a {}
class c : b {}

wie in meinem Fall wollte ich diese Klasse machen b: Form (yep die windows.forms) Klasse c: b {}

weil die Hälfte der Funktion identisch war und mit der Schnittstelle u müssen Sie alle neu schreiben

Ariel
quelle
1
Ihr Beispiel zeigt keine Mehrfachvererbung. Welches Problem versuchen Sie also zu lösen? Ein echtes Beispiel für Mehrfachvererbung würde zeigen class a : b, c(Implementierung aller erforderlichen Vertragslücken). Vielleicht sind Ihre Beispiele etwas zu stark vereinfacht?
M.Babcock
0

Da die Frage der Mehrfachvererbung (MI) von Zeit zu Zeit auftaucht, möchte ich einen Ansatz hinzufügen, der einige Probleme mit dem Kompositionsmuster behebt.

Ich baue auf den IFirst, ISecond, First, Second, FirstAndSecondAnsatz, wie es in der Frage vorgelegt wurde. Ich reduziere den Beispielcode auf IFirst, da das Muster unabhängig von der Anzahl der Schnittstellen / MI-Basisklassen gleich bleibt.

Nehmen wir an, dass mit MI Firstund Secondbeide von derselben Basisklasse abgeleitet werden BaseClass, wobei nur öffentliche Schnittstellenelemente von verwendet werdenBaseClass

Dies kann durch Hinzufügen eines Containerverweises BaseClassin Firstund SecondImplementierung ausgedrückt werden :

class First : IFirst {
  private BaseClass ContainerInstance;
  First(BaseClass container) { ContainerInstance = container; }
  public void FirstMethod() { Console.WriteLine("First"); ContainerInstance.DoStuff(); } 
}
...

Die Dinge werden komplizierter, wenn auf geschützte Schnittstellenelemente BaseClassverwiesen wird oder wenn Firstund wenn Secondes sich um abstrakte Klassen in MI handelt, deren Unterklassen einige abstrakte Teile implementieren müssen.

class BaseClass {
  protected void DoStuff();
}

abstract class First : IFirst {
  public void FirstMethod() { DoStuff(); DoSubClassStuff(); }
  protected abstract void DoStuff(); // base class reference in MI
  protected abstract void DoSubClassStuff(); // sub class responsibility
}

Mit C # können verschachtelte Klassen auf geschützte / private Elemente ihrer enthaltenen Klassen zugreifen, sodass die abstrakten Bits aus der FirstImplementierung verknüpft werden können .

class FirstAndSecond : BaseClass, IFirst, ISecond {
  // link interface
  private class PartFirst : First {
    private FirstAndSecond ContainerInstance;
    public PartFirst(FirstAndSecond container) {
      ContainerInstance = container;
    }
    // forwarded references to emulate access as it would be with MI
    protected override void DoStuff() { ContainerInstance.DoStuff(); }
    protected override void DoSubClassStuff() { ContainerInstance.DoSubClassStuff(); }
  }
  private IFirst partFirstInstance; // composition object
  public FirstMethod() { partFirstInstance.FirstMethod(); } // forwarded implementation
  public FirstAndSecond() {
    partFirstInstance = new PartFirst(this); // composition in constructor
  }
  // same stuff for Second
  //...
  // implementation of DoSubClassStuff
  private void DoSubClassStuff() { Console.WriteLine("Private method accessed"); }
}

Es gibt einige Probleme, aber wenn die tatsächliche Implementierung von FirstMethod und SecondMethod ausreichend komplex ist und die Anzahl der privaten / geschützten Methoden, auf die zugegriffen wird, moderat ist, kann dieses Muster dazu beitragen, das Fehlen einer Mehrfachvererbung zu überwinden.

grek40
quelle
0

Dies entspricht der Antwort von Lawrence Wenham, kann jedoch je nach Anwendungsfall eine Verbesserung darstellen oder auch nicht - Sie benötigen keine Setter.

public interface IPerson {
  int GetAge();
  string GetName();
}

public interface IGetPerson {
  IPerson GetPerson();
}

public static class IGetPersonAdditions {
  public static int GetAgeViaPerson(this IGetPerson getPerson) { // I prefer to have the "ViaPerson" in the name in case the object has another Age property.
    IPerson person = getPerson.GetPersion();
    return person.GetAge();
  }
  public static string GetNameViaPerson(this IGetPerson getPerson) {
    return getPerson.GetPerson().GetName();
  }
}

public class Person: IPerson, IGetPerson {
  private int Age {get;set;}
  private string Name {get;set;}
  public IPerson GetPerson() {
    return this;
  }
  public int GetAge() {  return Age; }
  public string GetName() { return Name; }
}

Jetzt kann jedes Objekt, das weiß, wie man eine Person erhält, IGetPerson implementieren und verfügt automatisch über die Methoden GetAgeViaPerson () und GetNameViaPerson (). Von diesem Punkt an geht im Grunde der gesamte Personencode in IGetPerson, nicht in IPerson, außer in neue ivars, die in beide gehen müssen. Bei der Verwendung eines solchen Codes müssen Sie sich keine Gedanken darüber machen, ob Ihr IGetPerson-Objekt selbst tatsächlich eine IPerson ist oder nicht.

William Jockusch
quelle
0

Dies ist jetzt über partialKlassen möglich . Jede von ihnen kann eine Klasse für sich erben, sodass das endgültige Objekt alle Basisklassen erbt. Mehr dazu erfahren Sie hier .

Nekuś
quelle