Wielokrotne dziedziczenie w C #

212

Ponieważ wielokrotne dziedziczenie jest złe (komplikuje to źródło), C # nie zapewnia takiego wzorca bezpośrednio. Ale czasami pomocne byłoby posiadanie tej umiejętności.

Na przykład jestem w stanie zaimplementować brakujący wzorzec wielokrotnego dziedziczenia za pomocą interfejsów i trzech takich klas:

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

Za każdym razem, gdy dodam metodę do jednego z interfejsów, muszę również zmienić klasę FirstAndSecond .

Czy istnieje sposób na wstrzyknięcie wielu istniejących klas do jednej nowej klasy, tak jak jest to możliwe w C ++?

Może istnieje rozwiązanie wykorzystujące generowanie kodu?

Lub może to wyglądać tak (wyobrażona składnia c #):

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

Aby nie było potrzeby aktualizowania klasy FirstAndSecond podczas modyfikowania jednego z interfejsów.


EDYTOWAĆ

Może lepiej rozważyć praktyczny przykład:

Masz istniejącą klasę (np. Tekstowy klient TCP oparty na ITextTcpClient), którego już używasz w różnych lokalizacjach w projekcie. Teraz czujesz potrzebę stworzenia komponentu swojej klasy, aby był łatwo dostępny dla programistów formularzy Windows.

O ile wiem, obecnie masz na to dwa sposoby:

  1. Napisz nową klasę, która jest dziedziczona ze składników i implementuje interfejs klasy TextTcpClient, używając instancji samej klasy, jak pokazano za pomocą FirstAndSecond.

  2. Napisz nową klasę, która dziedziczy z TextTcpClient i jakoś implementuje IComponent (jeszcze tego nie próbowałem).

W obu przypadkach musisz wykonać pracę według metody, a nie według klasy. Ponieważ wiesz, że będziemy potrzebować wszystkich metod TextTcpClient i Component, najłatwiejszym rozwiązaniem byłoby połączenie tych dwóch metod w jedną klasę.

Aby uniknąć konfliktów, można to zrobić przez wygenerowanie kodu, w którym wynik może być później zmieniony, ale ręczne wpisanie tego jest czystym bólem w dupie.

Jaskółka oknówka
źródło
W zakresie, w jakim nie jest to po prostu wielokrotne dziedziczenie w przebraniu, w jaki sposób jest to mniej skomplikowane?
harpo
Biorąc pod uwagę nowe metody rozszerzeń w wersji 3.5 i sposób, w jaki działa (statyczne generowanie wywołań elementów), może to być jedna z następnych zmian w języku .NET.
Larry
Czasami zastanawiam się, dlaczego ludzie po prostu nie robią ... klasy A: klasy B: klasy C?
Chibueze Opata
@NazarMerza: Link się zmienił. Teraz: problem z wielokrotnym dziedziczeniem .
Craig McQueen,
9
Nie daj się zwieść propagandzie. Twój przykład pokazuje, że wielokrotne dziedziczenie jest przydatne, a interfejsy są tylko obejściem dla jego braku
Kemal Erdogan

Odpowiedzi:

125

Ponieważ wielokrotne dziedziczenie jest złe (komplikuje to źródło), C # nie zapewnia takiego wzorca bezpośrednio. Ale czasami pomocne byłoby posiadanie tej umiejętności.

C # i CLR .net nie wdrożyły MI, ponieważ nie doszły do ​​wniosku, w jaki sposób będzie on współdziałał między C #, VB.net i innymi językami, a nie dlatego, że „skomplikowałoby to źródło”

MI jest użyteczną koncepcją, na które nie odpowiedziano na pytania: - „Co robisz, gdy masz wiele wspólnych klas podstawowych w różnych superklasach?

Perl jest jedynym językiem, w którym kiedykolwiek pracowałem, w którym MI działa i działa dobrze. .Net może go kiedyś wprowadzić, ale jeszcze nie, CLR już obsługuje MI, ale jak już powiedziałem, nie ma dla niego żadnych innych konstrukcji językowych.

Do tego czasu utkniesz z obiektami proxy i wieloma interfejsami :(

IanNorton
źródło
39
CLR nie obsługuje wielokrotnego dziedziczenia implementacji, tylko dziedziczenie wielu interfejsów (które jest również obsługiwane w języku C #).
Jordão
4
@ Jordão: Dla kompletności: kompilatory mogą tworzyć MI dla swoich typów w CLR. Ma swoje zastrzeżenia, na przykład nie jest zgodny z CLS. Aby uzyskać więcej informacji, zobacz ten artykuł (2004) blogs.msdn.com/b/csharpfaq/archive/2004/03/07/…
dvdvorle
2
@MrHappy: Bardzo interesujący artykuł. Rzeczywiście zbadałem jakiś sposób kompozycji cechy dla C #, spójrz.
Jordão,
10
@ MandeepJanjua Nie twierdziłem, że coś takiego, powiedziałem „mogę to dobrze wprowadzić”. Faktem jest, że CLR w standardzie ECMA zapewnia maszynerię IL do wielokrotnego dziedziczenia, tyle że nic z niej w pełni nie korzysta.
IanNorton
4
Wielokrotne dziedziczenie FYI nie jest złe i nie komplikuje kodu. Pomyślałem, że o tym wspomnę.
Dmitri Nesteruk
214

Zastanów się nad użyciem kompozycji zamiast symulowania wielokrotnego dziedziczenia. Interfejsów można użyć do zdefiniowania klas, które składają się na kompozycję, np .: ISteerableimplikuje właściwość typu SteeringWheel, IBrakableimplikuje właściwość typu BrakePedalitp.

Gdy to zrobisz, możesz użyć funkcji Extension Methods dodanej do C # 3.0, aby dodatkowo uprościć metody wywoływania tych domyślnych właściwości, np .:

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
źródło
13
O to jednak chodzi - taki pomysł ułatwiłby komponowanie.
Jon Skeet
9
Tak, ale są przypadki użycia, w których naprawdę potrzebujesz metod jako części głównego obiektu
David Pierre
9
Niestety dane zmiennych członka nie są dostępne w metodach rozszerzenia, więc musisz je ujawnić jako wewnętrzne lub (ug) publiczne, chociaż myślę, że składanie według umowy jest najlepszym sposobem rozwiązania wielokrotnego dziedziczenia.
cfeduke
4
Doskonała odpowiedź! Zwięzła, łatwa do zrozumienia, bardzo przydatna ilustracja. Dziękuję Ci!
AJ.
3
myCarPrzed zadzwonieniem możemy chcieć sprawdzić, czy skończył kierowanie w lewo Stop. Może się przewrócić, jeśli zostanie Stopzastosowany, gdy myCarjest nadmiernie szybki. : D
Devraj Gadhavi
16

Stworzyłem post-kompilator C #, który umożliwia takie rzeczy:

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

Możesz uruchomić post-kompilator jako wydarzenie po kompilacji Visual Studio:

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

W tym samym zestawie używasz go w następujący sposób:

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

W innym zestawie używasz go w następujący sposób:

var fas = new FirstAndSecond();
fas.FirstMethod();
fas.SecondMethod();
Jordão
źródło
6

Możesz mieć jedną abstrakcyjną klasę bazową, która implementuje zarówno IFirst, jak i ISecond, a następnie dziedziczy po tej właśnie podstawie.

Joel Coehoorn
źródło
Jest to prawdopodobnie najlepsze rozwiązanie, ale niekoniecznie najlepszy pomysł: p
leppie
1
czy nadal nie musiałbyś edytować klasy abstrakcyjnej, kiedy dodajesz metody do interfcesów?
Rik
Rik: jak leniwy jesteś, kiedy musisz to zrobić tylko raz?
leppie
3
@leppie - „Za każdym razem, gdy dodam metodę do jednego z interfejsów, muszę również zmienić klasę FirstAndSecond”. Ta część pierwotnego pytania nie jest objęta tym rozwiązaniem, prawda?
Rik
2
Będziesz musiał edytować klasę abstrakcyjną, ale NIE musisz edytować żadnych innych klas, które od niej zależą. Buck zatrzymuje się na tym, zamiast kontynuować kaskadowanie do całej kolekcji klas.
Joel Coehoorn,
3

MI NIE jest zły, każdy, kto go (poważnie) użył, UWIELBIA go i NIE komplikuje kodu! Przynajmniej nie więcej niż inne konstrukcje mogą skomplikować kod. Zły kod to zły kod niezależnie od tego, czy MI jest na zdjęciu.

W każdym razie mam fajne małe rozwiązanie dla wielokrotnego dziedziczenia, którym chciałem się podzielić, jest na; http://ra-ajax.org/lsp-liskov-substitution-principle-to-be-or-not-to-be.blog lub możesz kliknąć link w moim sig ... :)

Thomas Hansen
źródło
Czy możliwe jest wielokrotne dziedziczenie, a jednocześnie upcasty i downcasty zachowują tożsamość? Rozwiązania znam za problemy wielokrotnego dziedziczenia krążą wokół konieczności odlewane, które nie są zachowujący tożsamość (jeśli myFoojest typu Foo, który dziedziczy Mooi Goo, które dziedziczą zarówno z Boo, wtedy (Boo)(Moo)myFooi (Boo)(Goo)myFoonie byłoby równoważne). Czy znasz jakieś metody zachowania tożsamości?
supercat
1
Możesz użyć web.archive.org lub podobnego, aby skorzystać z linku, ale okazuje się, że jest to bardziej szczegółowa dyskusja na temat dokładnie rozwiązania oferowanego w pierwotnym pytaniu tutaj.
MikeBeaton
2

W mojej własnej implementacji odkryłem, że używanie klas / interfejsów dla MI, chociaż „dobra forma”, było na ogół nadmiernym komplikowaniem, ponieważ musisz skonfigurować całe to wielokrotne dziedziczenie tylko dla kilku niezbędnych wywołań funkcji, aw moim przypadku trzeba to robić dosłownie kilkadziesiąt razy nadmiarowo.

Zamiast tego łatwiej było po prostu tworzyć statyczne „funkcje, które wywołują funkcje, które wywołują funkcje” w różnych odmianach modułowych, jako rodzaj zamiany OOP. Rozwiązaniem, nad którym pracowałem, był „system zaklęć” dla RPG, w którym efekty muszą silnie wymieszać i wywoływać funkcje, aby dać ekstremalną różnorodność zaklęć bez ponownego pisania kodu, podobnie jak wskazuje na to przykład.

Większość funkcji może być teraz statyczna, ponieważ niekoniecznie potrzebuję instancji dla logiki pisowni, podczas gdy dziedziczenie klas nie może nawet używać wirtualnych lub abstrakcyjnych słów kluczowych, gdy są statyczne. Interfejsy nie mogą ich w ogóle używać.

Kodowanie wydaje się znacznie szybsze i czystsze w ten sposób IMO. Jeśli po prostu wykonujesz funkcje i nie potrzebujesz odziedziczonych właściwości , użyj funkcji.

hydrix
źródło
2

Z C # 8 masz teraz praktycznie wielokrotne dziedziczenie poprzez domyślną implementację elementów interfejsu:

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
źródło
4
Tak, ale zauważ, że w powyższym przypadku nie będziesz w stanie tego zrobić new ConsoleLogger().Log(someEception)- po prostu nie zadziała, musisz jawnie rzucić obiekt na, ILoggeraby użyć domyślnej metody interfejsu. Więc jego użyteczność jest nieco ograniczona.
Dmitri Nesteruk
1

Jeśli możesz żyć z zastrzeżeniem, że metody IFirst i ISecond muszą wchodzić w interakcje tylko z umową IFirst i ISecond (jak w twoim przykładzie) ... możesz zrobić to, o co prosisz metodami rozszerzenia. W praktyce rzadko tak się dzieje.

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

Zatem podstawową ideą jest zdefiniowanie wymaganej implementacji w interfejsach ... te wymagane elementy powinny wspierać elastyczną implementację w metodach rozszerzenia. Za każdym razem, gdy trzeba „dodać metody do interfejsu”, należy dodać metodę rozszerzenia.

Amy B.
źródło
1

Tak, używanie interfejsu jest kłopotliwe, ponieważ za każdym razem, gdy dodamy metodę w klasie, musimy dodać podpis w interfejsie. A co jeśli mamy już klasę z wieloma metodami, ale bez interfejsu? musimy ręcznie utworzyć interfejs dla wszystkich klas, z których chcemy dziedziczyć. A najgorsze jest to, że musimy implementować wszystkie metody w interfejsach w klasie potomnej, jeśli klasa potomna ma dziedziczyć z wielu interfejsów.

Postępując zgodnie z wzorcem projektowania elewacji, możemy symulować dziedziczenie po wielu klasach za pomocą akcesoriów . Zadeklaruj klasy jako właściwości za pomocą {get; set;} wewnątrz klasy, która musi dziedziczyć, a wszystkie publiczne właściwości i metody pochodzą z tej klasy, a w konstruktorze klasy podrzędnej tworzy instancję klas nadrzędnych.

Na przykład:

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


 }

dzięki tej strukturze klasa Dziecko będzie mieć dostęp do wszystkich metod i właściwości klasy Ojciec i Matka, symulując wielokrotne dziedziczenie, dziedzicząc instancję klas nadrzędnych. Niezupełnie to samo, ale jest praktyczne.

Jog
źródło
2
Nie zgadzam się z pierwszym akapitem. Do interfejsu dodajesz tylko sygnatury metod, które chcesz w KAŻDEJ klasie. Możesz jednak dodać dowolną liczbę dodatkowych metod do dowolnych klas. Istnieje również interfejs wyodrębniania prawym przyciskiem myszy, który ułatwia wyodrębnianie interfejsów. Wreszcie twój przykład nie jest w żaden sposób dziedziczony (wielokrotny lub inny), jest jednak doskonałym przykładem kompozycji. Niestety, miałoby to więcej zalet, gdybyś użył interfejsów do zademonstrowania DI / IOC przy użyciu konstruktora / zastrzyku właściwości. Chociaż nie będę głosował, nie sądzę, że to dobra odpowiedź przepraszam.
Francis Rodgers,
1
Patrząc wstecz na ten wątek rok później, zgadzam się, że możesz dodać tyle metod, ile chcesz w klasie, bez dodawania podpisu w interfejsie, jednak spowodowałoby to niekompletność interfejsu. Poza tym nie byłem w stanie znaleźć interfejsu IDE - wyodrębnić kliknięcie prawym przyciskiem myszy, być może czegoś mi brakowało. Ale moim większym zmartwieniem jest to, że kiedy podasz podpis w interfejsie, klasy dziedziczące muszą go zaimplementować. Myślę, że jest to podwójna praca i może prowadzić do powielania kodów.
Jogowie,
WYCIĄGNIJ INTERFEJS: kliknij prawym przyciskiem myszy podpis klasy, a następnie wyodrębnij interfejs ... w VS2015 ten sam proces, z wyjątkiem tego, że musisz kliknąć prawym przyciskiem myszy, a następnie wybierz Quick Actions and Refactorings...to trzeba wiedzieć, zaoszczędzi ci to dużo czasu
Chef_Code
1

Wydaje się, że wszyscy zmierzamy w tym kierunku, ale oczywistą inną możliwością, tutaj, jest zrobienie tego, co OOP powinno robić, i zbudowanie twojego drzewa dziedziczenia ... (czy nie to wszystko jest projektem klasy o?)

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

Ta struktura zapewnia bloki kodu wielokrotnego użytku i na pewno, jak należy pisać kod OOP?

Jeśli to podejście nie do końca pasuje, po prostu tworzymy nowe klasy w oparciu o wymagane obiekty ...

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; }
}
Paweł
źródło
0

Wielokrotne dziedziczenie jest jedną z tych rzeczy, które generalnie powodują więcej problemów niż rozwiązuje. W C ++ pasuje to do tego, że dajesz wystarczająco dużo liny do powieszenia się, ale Java i C # wybrali bezpieczniejszą drogę, nie dając ci opcji. Największym problemem jest to, co zrobić, jeśli odziedziczysz wiele klas, które mają metodę z tą samą sygnaturą, której dziedziczenie nie implementuje. Którą metodę klasy wybrać? Czy to nie powinno się kompilować? Generalnie istnieje inny sposób implementacji większości rzeczy, który nie polega na wielokrotnym dziedziczeniu.

tloach
źródło
8
Proszę nie oceniać MI według C ++, to tak, jakby oceniać OOP przez PHP lub samochody od Pintos. Problem ten można łatwo rozwiązać: w programie Eiffel, kiedy dziedziczysz po klasie, musisz również określić, które metody chcesz dziedziczyć i możesz zmienić ich nazwę. Brak dwuznaczności i niespodzianek.
Jörg W Mittag,
2
@mP: nie, Eiffel zapewnia prawdziwe wielokrotne dziedziczenie implementacji. Zmiana nazwy nie oznacza utraty łańcucha dziedziczenia, ani nie spowoduje utraty rzutu klas.
Abel
0

Jeśli X dziedziczy po Y, ma to dwa nieco ortogonalne efekty:

  1. Y zapewni domyślną funkcjonalność dla X, więc kod dla X musi zawierać tylko rzeczy inne niż Y.
  2. Prawie w każdym miejscu można by oczekiwać Y, zamiast tego można użyć X.

Chociaż dziedziczenie zapewnia obie funkcje, nietrudno wyobrazić sobie okoliczności, w których jedna z nich mogłaby być użyteczna bez drugiej. W żadnym znanym mi języku .net nie ma bezpośredniego sposobu implementacji pierwszego bez drugiego, chociaż można by uzyskać taką funkcjonalność, definiując klasę podstawową, która nigdy nie jest używana bezpośrednio, i posiadając jedną lub więcej klas, które dziedziczą bezpośrednio z niej bez dodawania czegokolwiek nowy (takie klasy mogłyby dzielić cały swój kod, ale nie mogłyby się wzajemnie zastępować). Dowolny język zgodny z CLR pozwoli jednak na użycie interfejsów, które zapewniają drugą funkcję interfejsów (zastępowalność) bez pierwszego (ponowne użycie elementu).

supercat
źródło
0

Wiem, że wiem, mimo że jest to niedozwolone i tak dalej, czasami naprawdę potrzebujesz go dla tych:

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

tak jak w moim przypadku chciałem zrobić tę klasę b: Formularz (tak. windows.forms) klasa c: b {}

ponieważ połowa funkcji była identyczna i przy interfejsie musisz je wszystkie przepisać

ariel
źródło
1
Twój przykład nie przedstawiają wielokrotne dziedziczenie, co z tego problemu są Państwo stara się rozwiązać? Pokazałby się prawdziwy przykład wielokrotnego dziedziczenia class a : b, c(wdrażanie wszelkich niezbędnych dziur w umowie). Być może twoje przykłady są nieco uproszczone?
M.Babcock
0

Ponieważ od czasu do czasu pojawia się pytanie o wielokrotne dziedziczenie (MI), chciałbym dodać podejście, które rozwiązuje niektóre problemy ze schematem kompozycji.

Buduję na IFirst, ISecond, First, Second, FirstAndSecondpodejścia, jak to zostało przedstawione w pytaniu. Zmniejszam przykładowy kod do IFirst, ponieważ wzorzec pozostaje taki sam bez względu na liczbę interfejsów / klas podstawowych MI.

Załóżmy, że z MI Firsti Secondoba wywodzą się z tej samej klasy podstawowej BaseClass, używając tylko elementów interfejsu publicznegoBaseClass

Można to wyrazić, dodając odwołanie do kontenera BaseClassw implementacji Firsti Second:

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

Sprawy stają się bardziej skomplikowane, gdy BaseClassodwołuje się do chronionych elementów interfejsu lub kiedy Firsti Secondbyłyby klasami abstrakcyjnymi w MI, wymagającymi od ich podklas zaimplementowania niektórych abstrakcyjnych części.

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
}

C # pozwala klasom zagnieżdżonym na dostęp do chronionych / prywatnych elementów ich klas zawierających, więc można tego użyć do połączenia abstrakcyjnych bitów z Firstimplementacji.

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

W grę wchodzi sporo kotłów, ale jeśli rzeczywista implementacja FirstMethod i SecondMethod jest wystarczająco złożona, a ilość dostępnych metod prywatnych / chronionych jest umiarkowana, to ten wzór może pomóc w przezwyciężeniu braku wielokrotnego dziedziczenia.

grek40
źródło
0

Jest to zgodne z odpowiedzią Lawrence'a Wenhama, ale w zależności od przypadku użycia może, ale nie musi być poprawą - nie potrzebujesz seterów.

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

Teraz każdy obiekt, który wie, jak uzyskać osobę, może zaimplementować IGetPerson i automatycznie będzie miał metody GetAgeViaPerson () i GetNameViaPerson (). Od tego momentu zasadniczo cały kod Osoby przechodzi do IGetPerson, a nie do IPerson, oprócz nowych ivarów, które muszą przejść do obu. Korzystając z takiego kodu, nie musisz się martwić, czy sam obiekt IGetPerson jest w rzeczywistości IPersonem.

William Jockusch
źródło
0

Jest to teraz możliwe dzięki partialklasom przejściowym, każda z nich może dziedziczyć klasę samodzielnie, dzięki czemu ostateczny obiekt dziedziczy wszystkie klasy podstawowe. Możesz dowiedzieć się więcej na ten temat tutaj .

Nekuś
źródło