Zmniejszenie płyty kotłowej w klasie, która implementuje interfejsy poprzez kompozycję

11

Mam klasę: Ajest to połączenie wielu mniejszych klas B, Ci D.

B, CI Dwdrożyć interfejsów IB, ICoraz IDodpowiednio.

Ponieważ Aobsługuje wszystkie funkcje B, Ci D, Anarzędzi IB, ICa IDtakże, ale to niestety prowadzi do wielu re-routing w realizacjiA

Tak jak:

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;}
    }
}

Czy istnieje sposób, aby pozbyć się przekierowania z płyty A? B, Ci Dwszystkie implementują różne, ale wspólne funkcje, i podoba mi się to Aimplementacja IB, ICa IDponieważ oznacza to, że mogę przekazać Asamą siebie jako zależność pasującą do tych interfejsów i nie muszę ujawniać wewnętrznych metod pomocniczych.

Nick Udell
źródło
Czy możesz powiedzieć coś o tym, dlaczego potrzebujesz klasy A do wdrożenia IB, IC, ID? Dlaczego nie ujawnić BCD jako publicznego?
Esben Skov Pedersen
1
Myślę, że mój główny powód preferując Awdrożyć IB, ICa IDto wydaje się bardziej sensowne napisać Person.Walk()w przeciwieństwie do Person.WalkHelper.Walk(), na przykład.
Nick Udell

Odpowiedzi:

7

To, czego szukasz, jest powszechnie nazywane mixinami . Niestety C # natywnie nie obsługuje tych.

Istnieje kilka obejść: jeden , dwa , trzy i wiele innych.

Naprawdę podoba mi się ostatni. Pomysł użycia automatycznie generowanej klasy cząstkowej do wygenerowania płyty kotłowej jest prawdopodobnie najbliższy naprawdę dobremu rozwiązaniu:

[pMixins] to wtyczka Visual Studio, która skanuje rozwiązanie dla klas częściowych ozdobionych atrybutami pMixin. Oznaczając klasę jako częściową, [pMixins] może utworzyć plik za kodem i dodać dodatkowych członków do klasy

Euforyk
źródło
2

Chociaż nie redukuje to w samym kodzie, Visual Studio 2015 zawiera teraz opcję refaktoryzacji, aby automatycznie wygenerować dla ciebie płytkę.

Aby z tego skorzystać, najpierw utwórz interfejs IExamplei implementację Example. Następnie utwórz nową klasę Compositei wprowadź ją do dziedziczenia IExample, ale nie implementuj interfejsu.

Dodaj właściwość lub pole typu Exampledo swojej Compositeklasy, otwórz menu szybkich działań na tokenie IExamplew Compositepliku klasy i wybierz „Implementuj interfejs poprzez„ Przykład ”, gdzie„ Przykład ”w tym przypadku jest nazwą pola lub właściwości.

Należy pamiętać, że chociaż program Visual Studio wygeneruje i przekieruje wszystkie metody i właściwości zdefiniowane w interfejsie do tej klasy pomocniczej, nie przekieruje zdarzeń w momencie opublikowania tej odpowiedzi.

Nick Udell
źródło
1

Twoja klasa robi za dużo, dlatego musisz zaimplementować tak wiele płyt grzewczych. W komentarzach mówisz:

bardziej sensowniej jest pisać Person.Walk () niż Person.WalkHelper.Walk ()

Nie zgadzam się. Chodzenie prawdopodobnie wiąże się z wieloma regułami i nie należy do Personklasy - należy do WalkingServiceklasy, w walk(IWalkable)której metoda implementuje Osoba IWalkable.

Podczas pisania kodu zawsze miej na uwadze SOLIDNE zasady. Dwa, które są tutaj najbardziej odpowiednie, to Separation of Concerns (wyodrębnij kod przejścia do osobnej klasy) i Segregacja interfejsu (podzielone interfejsy na określone zadania / funkcje / umiejętności). Wygląda na to, że możesz mieć część I z wieloma interfejsami, ale potem cofasz całą swoją dobrą pracę, wprowadzając je w jednej klasie.

Stuart Leyland-Cole
źródło
W moim przykładzie funkcjonalność jest zaimplementowana w osobnych klasach i mam klasę złożoną z kilku z nich jako sposób grupowania wymaganego zachowania. Żadna z rzeczywistych implementacji nie istnieje w mojej większej klasie, wszystkie są obsługiwane przez mniejsze komponenty. Być może masz rację, a upublicznienie tych klas pomocników, aby można było z nimi bezpośrednio wchodzić w interakcję, jest właściwym rozwiązaniem.
Nick Udell
4
Jeśli chodzi o ideę walk(IWalkable)metody, osobiście jej nie lubię, ponieważ wówczas usługi chodzenia stają się obowiązkiem dzwoniącego, a nie Osoby, a konsekwencja nie jest gwarantowana. Każdy dzwoniący musiałby wiedzieć, z której usługi skorzystać, aby zagwarantować spójność, podczas gdy zachowanie jej w klasie Person oznacza, że ​​należy ją ręcznie zmienić, aby zachować zachowanie podczas chodzenia, jednocześnie umożliwiając odwrócenie zależności.
Nick Udell
0

To, czego szukasz, to wielokrotne dziedziczenie. Jednak ani C #, ani Java nie mają tego.

Możesz przedłużyć B od A. To wyeliminuje potrzebę prywatnego pola dla B, a także kleju przekierowującego. Ale możesz to zrobić tylko dla jednego z B, C lub D.

Teraz, jeśli możesz sprawić, że C odziedziczy po B, a D po C, to musisz mieć tylko A przedłużyć D ...;) - dla jasności, tak naprawdę nie zachęcam do tego w tym przykładzie, ponieważ nic nie wskazuje dla tego.

W C ++ możesz mieć A odziedziczone po B, C i D.

Ale wielokrotne dziedziczenie sprawia, że ​​programy są trudniejsze do uzasadnienia i ma swoje własne problemy; Ponadto często istnieje czysty sposób, aby tego uniknąć. Mimo to, czasami bez niego, konieczne jest dokonanie kompromisów projektowych między powtarzającym się szablonem a sposobem wyświetlania modelu obiektowego.

To trochę tak, jakbyś próbował połączyć kompozycję i dziedzictwo. Gdyby to był C ++, można użyć czystego rozwiązania dziedziczenia. Ale skoro tak nie jest, polecam przyjęcie kompozycji.

Erik Eidt
źródło
2
Możesz mieć wielokrotne dziedziczenie za pomocą interfejsów zarówno w Javie, jak i c #, tak jak operacja zrobiła w jego przykładowym kodzie.
Robert Harvey
1
Niektórzy mogą rozważyć implementację interfejsu tak samo jak wielokrotne dziedziczenie (jak powiedzmy w C ++). Inni by się nie zgodzili, ponieważ interfejsy nie mogą przedstawiać metod instancji dziedziczonych, co byś miał, gdybyś miał wiele dziedziczeń. W języku C # nie można rozszerzać wielu klas bazowych, dlatego nie można dziedziczyć implementacji metod instancji z wielu klas, dlatego potrzebujesz wszystkich
podstaw
Zgadzam się, że wielokrotne dziedziczenie jest złe, ale C # / Java alternatywa pojedynczego dziedziczenia z implementacją wielu interfejsów nie jest lekarstwem na wszystko (bez przekazywania interfejsu jest to ergonomiczny koszmar). Po teraz spędził trochę czasu z Golang, myślę, że Go mam prawo pomysł nie mając żadnych spadków i zamiast polegać wyłącznie na składzie z interfejsem-spedycji - ale ich szczególności realizacja ma problemy też. Myślę, że najlepszym rozwiązaniem (którego chyba nikt jeszcze nie wdrożył) jest kompozycja z przekazywaniem, z możliwością użycia dowolnego typu jako interfejsu.
Dai