Reduzierung der Boilerplate in der Klasse, die Schnittstellen durch Komposition implementiert

10

Ich habe eine Klasse: Adas ist eine Mischung aus mehreren kleineren Klassen B, Cund D.

B, CUnd DSchnittstellen implementieren IB, ICund IDjeweils.

Da Aunterstützt alle Funktionen von B, Cund D, Aimplementiert IB, ICund IDauch, aber dies leider führt zu einer viel Umleiten bei der Umsetzung derA

Wie so:

interface IB
{
    int Foo {get;}
}

public class B : IB
{
    public int Foo {get {return 5;}}
}

interface IC
{
    void Bar();
}

public class C : IC
{
    public void Bar()
    {
    }
}

interface ID
{
    string Bash{get; set;}
}

public class D: ID
{
    public string Bash {get;set;}
}

class A : IB, IC, ID
{
    private B b = new B();
    private C c = new C();
    private D d = new D();

    public int Foo {get {return b.Foo}}

    public A(string bash)
    {
        Bash = bash;
    }

    public void Bar()
    {
        c.Bar();
    }

    public string Bash
    {
         get {return d.Bash;}
         set {d.Bash = value;}
    }
}

Gibt es eine Möglichkeit, diese Umleitung von Boilerplates in loszuwerden A? B, CUnd Dimplementieren alle unterschiedlich , aber gemeinsame Funktionalität, und ich mag , dass AGeräte IB, ICund IDweil es bedeutet , kann ich in übergeben Asich als Abhängigkeit diese Schnittstellen übereinstimmt, und müssen nicht die inneren Hilfsmethoden aus.

Nick Udell
quelle
Können Sie etwas darüber sagen, warum Sie eine Klasse A benötigen, um IB, IC, ID zu implementieren? Warum nicht einfach BCD öffentlich machen?
Esben Skov Pedersen
Ich glaube , meinen primären Grund für die Bevorzugung Aumzusetzen IB, ICund IDist es sinnvoller zu schreiben fühlt , Person.Walk()im Gegensatz zu Person.WalkHelper.Walk(), zum Beispiel.
Nick Udell

Antworten:

7

Was Sie suchen, wird allgemein als Mixins bezeichnet . Leider unterstützt C # diese nicht von Haus aus.

Es gibt nur wenige Problemumgehungen: eine , zwei , drei und viele mehr.

Ich mag den letzten wirklich sehr. Die Idee, automatisch generierte Teilklassen zum Generieren der Boilerplate zu verwenden, kommt wahrscheinlich einer tatsächlich guten Lösung am nächsten:

[pMixins] ist ein Visual Studio-Plug-In, das eine Lösung nach Teilklassen durchsucht, die mit pMixin-Attributen dekoriert sind. Durch Markieren Ihrer Klasse als Teil kann [pMixins] eine CodeBehind-Datei erstellen und Ihrer Klasse zusätzliche Mitglieder hinzufügen

Euphorisch
quelle
2

Visual Studio 2015 enthält jetzt eine Refactoring-Option, mit der das Boilerplate automatisch für Sie generiert werden kann.

Um dies zu verwenden, erstellen Sie zuerst Ihre Schnittstelle IExampleund Implementierung Example. Erstellen Sie dann Ihre neue Klasse Compositeund lassen Sie sie erben IExample, implementieren Sie die Schnittstelle jedoch nicht.

Fügen Sie ExampleIhrer CompositeKlasse eine Eigenschaft oder ein Feld vom Typ hinzu , öffnen Sie das Schnellmenü auf dem Token IExamplein Ihrer CompositeKlassendatei und wählen Sie "Schnittstelle durch 'Beispiel' implementieren", wobei 'Beispiel' in diesem Fall der Name des Felds oder der Eigenschaft ist.

Sie sollten beachten, dass Visual Studio zwar alle in der Schnittstelle definierten Methoden und Eigenschaften für diese Hilfsklasse generiert und umleitet, jedoch keine Ereignisse zum Zeitpunkt der Veröffentlichung dieser Antwort umleitet.

Nick Udell
quelle
1

Ihre Klasse macht zu viel, deshalb müssen Sie so viel Boilerplate implementieren. Sie sagen in den Kommentaren:

Es fühlt sich sinnvoller an, Person.Walk () zu schreiben als Person.WalkHelper.Walk ()

Ich stimme dir nicht zu. Das Gehen beinhaltet wahrscheinlich viele Regeln und gehört nicht zur PersonKlasse - es gehört zu einer WalkingServiceKlasse mit einer walk(IWalkable)Methode, die Person implementiert IWalkable.

Beachten Sie beim Schreiben von Code immer die SOLID-Prinzipien. Die beiden hier am besten geeigneten sind die Trennung von Bedenken (Extrahieren des Laufcodes in eine separate Klasse) und die Schnittstellentrennung (Aufteilung der Schnittstellen in bestimmte Aufgaben / Funktionen / Fähigkeiten). Es hört sich so an, als hätten Sie vielleicht einiges von I mit Ihren mehreren Schnittstellen, machen dann aber all Ihre gute Arbeit rückgängig, indem Sie sie von einer Klasse implementieren lassen.

Stuart Leyland-Cole
quelle
In meinem Beispiel die Funktionalität ist in separaten Klassen implementiert, und ich habe eine Klasse , bestehend aus ein paar von diesen als eine Möglichkeit , das erforderliche Verhalten der Gruppierung. Keine der tatsächlichen Implementierungen existiert in meiner größeren Klasse, alles wird von den kleineren Komponenten erledigt. Vielleicht haben Sie aber auch Recht, und diese Hilfsklassen öffentlich zu machen, damit sie direkt miteinander interagieren können, ist der richtige Weg.
Nick Udell
4
Die Idee einer walk(IWalkable)Methode gefällt mir persönlich nicht, da dann die Gehdienste in die Verantwortung des Anrufers und nicht der Person fallen und die Konsistenz nicht garantiert ist. Jeder Anrufer müsste wissen, welchen Dienst er verwenden soll, um die Konsistenz zu gewährleisten. Wenn er jedoch in der Person-Klasse bleibt, muss er manuell geändert werden, damit sich das Gehverhalten unterscheidet, und gleichzeitig die Abhängigkeitsinversion zulassen.
Nick Udell
0

Was Sie suchen, ist Mehrfachvererbung. Weder C # noch Java haben es jedoch.

Sie können B von A erweitern. Dadurch entfällt die Notwendigkeit eines privaten Felds für B sowie des Umleitungsklebers. Sie können dies jedoch nur für B, C oder D tun.

Wenn Sie nun C von B und D von C erben lassen können, müssen Sie nur A verlängern D ...;) - nur um klar zu sein, ich ermutige das in diesem Beispiel nicht wirklich, da es keinen Hinweis gibt dafür.

In C ++ kann A von B, C und D erben.

Die Mehrfachvererbung erschwert jedoch das Nachdenken über Programme und hat ihre eigenen Probleme. Außerdem gibt es oft einen sauberen Weg, dies zu vermeiden. Manchmal muss man jedoch ohne sie Design-Kompromisse zwischen der Wiederholung der Boilerplate und der Belichtung des Objektmodells eingehen.

Es fühlt sich ein bisschen so an, als würden Sie versuchen, Komposition und Vererbung zu mischen. Wenn dies C ++ wäre, könnten Sie eine reine Vererbungslösung verwenden. Aber da dies nicht der Fall ist, würde ich empfehlen, sich der Komposition zuzuwenden.

Erik Eidt
quelle
2
Sie können mehrere Vererbungen über Schnittstellen in Java und C # durchführen, genau wie es die Operation in seinem Beispielcode getan hat.
Robert Harvey
1
Einige ziehen möglicherweise die Implementierung einer Schnittstelle in Betracht, die der Mehrfachvererbung entspricht (wie beispielsweise in C ++). Andere würden dem nicht zustimmen, da Schnittstellen keine vererbbaren Instanzmethoden darstellen können. Dies ist der Fall, wenn Sie mehrere Vererbungen haben. In C # können Sie nicht mehrere Basisklassen erweitern, daher können Sie Instanzmethodenimplementierungen nicht von mehreren Klassen erben, weshalb Sie alle Boilerplate benötigen ...
Erik Eidt