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:
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.
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.
źródło
Odpowiedzi:
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 :(
źródło
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 .:
ISteerable
implikuje właściwość typuSteeringWheel
,IBrakable
implikuje właściwość typuBrakePedal
itp.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 .:
źródło
myCar
Przed zadzwonieniem możemy chcieć sprawdzić, czy skończył kierowanie w lewoStop
. Może się przewrócić, jeśli zostanieStop
zastosowany, gdymyCar
jest nadmiernie szybki. : DStworzyłem post-kompilator C #, który umożliwia takie rzeczy:
Możesz uruchomić post-kompilator jako wydarzenie po kompilacji Visual Studio:
W tym samym zestawie używasz go w następujący sposób:
W innym zestawie używasz go w następujący sposób:
źródło
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.
źródło
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 ... :)
źródło
myFoo
jest typuFoo
, który dziedziczyMoo
iGoo
, które dziedziczą zarówno zBoo
, wtedy(Boo)(Moo)myFoo
i(Boo)(Goo)myFoo
nie byłoby równoważne). Czy znasz jakieś metody zachowania tożsamości?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.
źródło
Z C # 8 masz teraz praktycznie wielokrotne dziedziczenie poprzez domyślną implementację elementów interfejsu:
źródło
new ConsoleLogger().Log(someEception)
- po prostu nie zadziała, musisz jawnie rzucić obiekt na,ILogger
aby użyć domyślnej metody interfejsu. Więc jego użyteczność jest nieco ograniczona.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.
///
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.
źródło
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:
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.
źródło
Quick Actions and Refactorings...
to trzeba wiedzieć, zaoszczędzi ci to dużo czasuWydaje 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?)
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 ...
źródło
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.
źródło
Jeśli X dziedziczy po Y, ma to dwa nieco ortogonalne efekty:
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).
źródło
Wiem, że wiem, mimo że jest to niedozwolone i tak dalej, czasami naprawdę potrzebujesz go dla tych:
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ć
źródło
class a : b, c
(wdrażanie wszelkich niezbędnych dziur w umowie). Być może twoje przykłady są nieco uproszczone?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
,FirstAndSecond
podejścia, jak to zostało przedstawione w pytaniu. Zmniejszam przykładowy kod doIFirst
, ponieważ wzorzec pozostaje taki sam bez względu na liczbę interfejsów / klas podstawowych MI.Załóżmy, że z MI
First
iSecond
oba wywodzą się z tej samej klasy podstawowejBaseClass
, używając tylko elementów interfejsu publicznegoBaseClass
Można to wyrazić, dodając odwołanie do kontenera
BaseClass
w implementacjiFirst
iSecond
:Sprawy stają się bardziej skomplikowane, gdy
BaseClass
odwołuje się do chronionych elementów interfejsu lub kiedyFirst
iSecond
byłyby klasami abstrakcyjnymi w MI, wymagającymi od ich podklas zaimplementowania niektórych abstrakcyjnych części.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
First
implementacji.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.
źródło
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.
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.
źródło
Jest to teraz możliwe dzięki
partial
klasom 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 .źródło