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:
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.
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.
quelle
Antworten:
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 :(
quelle
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.:
ISteerable
Impliziert eine Eigenschaft vom TypSteeringWheel
,IBrakable
impliziert eine Eigenschaft vom TypBrakePedal
usw.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:
quelle
myCar
die Lenkung nach links beendet ist, bevor wir anrufenStop
. Es kann überrollen, wenn es bei zu hoher GeschwindigkeitStop
angewendetmyCar
wird. : DIch habe einen C # -Post-Compiler erstellt , der Folgendes ermöglicht:
Sie können den Post-Compiler als Visual Studio-Post-Build-Ereignis ausführen:
In derselben Baugruppe verwenden Sie es folgendermaßen:
In einer anderen Baugruppe verwenden Sie es folgendermaßen:
quelle
Sie könnten eine abstrakte Basisklasse haben, die sowohl IFirst als auch ISecond implementiert und dann nur von dieser Basis erbt.
quelle
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 ... :)
quelle
myFoo
es sich um einen Typ handeltFoo
, der vonMoo
undGoo
beide erbtBoo
, dann(Boo)(Moo)myFoo
und(Boo)(Goo)myFoo
nicht gleichwertig). Kennen Sie identitätserhaltende Ansätze?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.
quelle
Mit C # 8 haben Sie jetzt praktisch mehrere Vererbungen über die Standardimplementierung von Schnittstellenmitgliedern:
quelle
new ConsoleLogger().Log(someEception)
- es wird einfach nicht funktionieren , Sie müssten Ihr Objekt explizit in a umwandelnILogger
, um die Standardschnittstellenmethode zu verwenden. Daher ist seine Nützlichkeit etwas eingeschränkt.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.
///
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.
quelle
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:
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.
quelle
Quick Actions and Refactorings...
dies ein wissen , ist , muss, wird es Ihnen viel Zeit sparenWir 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?)
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 ...
quelle
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.
quelle
Wenn X von Y erbt, hat dies zwei etwas orthogonale Effekte:
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.
quelle
Ich weiß, ich weiß, obwohl es nicht erlaubt ist und so weiter, manchmal brauchst du es tatsächlich so für die:
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
quelle
class a : b, c
(Implementierung aller erforderlichen Vertragslücken). Vielleicht sind Ihre Beispiele etwas zu stark vereinfacht?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
,FirstAndSecond
Ansatz, wie es in der Frage vorgelegt wurde. Ich reduziere den Beispielcode aufIFirst
, da das Muster unabhängig von der Anzahl der Schnittstellen / MI-Basisklassen gleich bleibt.Nehmen wir an, dass mit MI
First
undSecond
beide von derselben Basisklasse abgeleitet werdenBaseClass
, wobei nur öffentliche Schnittstellenelemente von verwendet werdenBaseClass
Dies kann durch Hinzufügen eines Containerverweises
BaseClass
inFirst
undSecond
Implementierung ausgedrückt werden :Die Dinge werden komplizierter, wenn auf geschützte Schnittstellenelemente
BaseClass
verwiesen wird oder wennFirst
und wennSecond
es sich um abstrakte Klassen in MI handelt, deren Unterklassen einige abstrakte Teile implementieren müssen.Mit C # können verschachtelte Klassen auf geschützte / private Elemente ihrer enthaltenen Klassen zugreifen, sodass die abstrakten Bits aus der
First
Implementierung verknüpft werden können .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.
quelle
Dies entspricht der Antwort von Lawrence Wenham, kann jedoch je nach Anwendungsfall eine Verbesserung darstellen oder auch nicht - Sie benötigen keine Setter.
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.
quelle
Dies ist jetzt über
partial
Klassen 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 .quelle