Załóżmy, że masz następującą sytuację
#include <iostream>
class Animal {
public:
virtual void speak() = 0;
};
class Dog : public Animal {
void speak() { std::cout << "woff!" <<std::endl; }
};
class Cat : public Animal {
void speak() { std::cout << "meow!" <<std::endl; }
};
void makeSpeak(Animal &a) {
a.speak();
}
int main() {
Dog d;
Cat c;
makeSpeak(d);
makeSpeak(c);
}
Jak widać, makeSpeak to procedura, która akceptuje ogólny obiekt Animal. W tym przypadku Animal jest dość podobny do interfejsu Java, ponieważ zawiera tylko czystą metodę wirtualną. makeSpeak nie zna natury zwierzęcia, które jest przekazywane. Po prostu wysyła sygnał „speak” i pozostawia późne powiązanie, aby zadecydować, która metoda wywołania: Cat :: speak () lub Dog :: speak (). Oznacza to, że w przypadku makeSpeak wiedza o tym, która podklasa jest faktycznie przekazywana, jest nieistotna.
Ale co z Pythonem? Zobaczmy kod dla tego samego przypadku w Pythonie. Zwróć uwagę, że staram się przez chwilę być jak najbardziej podobny do przypadku C ++:
class Animal(object):
def speak(self):
raise NotImplementedError()
class Dog(Animal):
def speak(self):
print "woff!"
class Cat(Animal):
def speak(self):
print "meow"
def makeSpeak(a):
a.speak()
d=Dog()
c=Cat()
makeSpeak(d)
makeSpeak(c)
Teraz w tym przykładzie widzisz tę samą strategię. Używasz dziedziczenia, aby wykorzystać hierarchiczną koncepcję psów i kotów będących zwierzętami. Ale w Pythonie nie ma potrzeby stosowania tej hierarchii. Działa to równie dobrze
class Dog:
def speak(self):
print "woff!"
class Cat:
def speak(self):
print "meow"
def makeSpeak(a):
a.speak()
d=Dog()
c=Cat()
makeSpeak(d)
makeSpeak(c)
W Pythonie możesz wysłać sygnał „przemów” do dowolnego obiektu, który chcesz. Jeśli obiekt jest w stanie sobie z nim poradzić, zostanie wykonany, w przeciwnym razie zgłosi wyjątek. Załóżmy, że dodajesz klasę Airplane do obu kodów i przesyłasz obiekt Airplane do makeSpeak. W przypadku C ++ nie będzie się kompilować, ponieważ Airplane nie jest pochodną klasą Animal. W przypadku Pythona zgłosi wyjątek w czasie wykonywania, co może być nawet oczekiwanym zachowaniem.
Z drugiej strony załóżmy, że dodajesz klasę MouthOfTruth za pomocą metody speak (). W przypadku C ++, albo będziesz musiał zmienić hierarchię, albo będziesz musiał zdefiniować inną metodę makeSpeak, aby akceptować obiekty MouthOfTruth, albo w Javie możesz wyodrębnić zachowanie do CanSpeakIface i zaimplementować interfejs dla każdego z nich. Rozwiązań jest wiele ...
Chciałabym zwrócić uwagę, że nie znalazłem jeszcze żadnego powodu, aby używać dziedziczenia w Pythonie (poza frameworkami i drzewami wyjątków, ale myślę, że istnieją alternatywne strategie). nie trzeba implementować hierarchii wyprowadzonej z bazy, aby wykonywać operacje polimorficzne. Jeśli chcesz użyć dziedziczenia do ponownego wykorzystania implementacji, możesz osiągnąć to samo poprzez zawieranie i delegowanie, z dodatkową korzyścią polegającą na tym, że możesz je zmienić w czasie wykonywania, i jasno zdefiniować interfejs zawartej implementacji, bez ryzyka niezamierzonych skutków ubocznych.
W końcu pytanie brzmi: jaki jest sens dziedziczenia w Pythonie?
Edycja : dzięki za bardzo interesujące odpowiedzi. Rzeczywiście można go użyć do ponownego wykorzystania kodu, ale zawsze jestem ostrożny podczas ponownego wykorzystywania implementacji. Ogólnie rzecz biorąc, mam tendencję do tworzenia bardzo płytkich drzew dziedziczenia lub braku drzewa, a jeśli funkcjonalność jest powszechna, refaktoryzuję ją jako wspólną procedurę modułu, a następnie wywołuję ją z każdego obiektu. Widzę zaletę posiadania jednego punktu zmiany (np. Zamiast dodawać do Dog, Cat, Moose itd., Po prostu dodaję do Animal, co jest podstawową zaletą dziedziczenia), ale możesz osiągnąć to samo z łańcuch delegacji (np. a la JavaScript). Nie twierdzę jednak, że jest lepiej, po prostu w inny sposób.
Znalazłem też podobny post na ten temat.