다음과 같은 상황이 있다고 가정합니다.
#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);
}
보시다시피 makeSpeak는 일반적인 Animal 객체를 받아들이는 루틴입니다. 이 경우 Animal은 순수한 가상 메소드 만 포함하므로 Java 인터페이스와 매우 유사합니다. makeSpeak는 전달되는 동물의 본질을 알지 못합니다. "speak"신호를 보내고 Cat :: speak () 또는 Dog :: speak () 중 호출 할 메서드를 처리하기 위해 후기 바인딩을 남겨 둡니다. 이것은 makeSpeak에 관한 한 실제로 어떤 하위 클래스가 전달되는지에 대한 지식은 관련이 없음을 의미합니다.
하지만 파이썬은 어떻습니까? Python에서 동일한 경우에 대한 코드를 살펴 보겠습니다. 잠시 동안 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)
이제이 예에서 동일한 전략을 볼 수 있습니다. 상속을 사용하여 개와 고양이 모두 동물이라는 계층 적 개념을 활용합니다. 하지만 파이썬에서는이 계층이 필요하지 않습니다. 이것은 똑같이 잘 작동합니다.
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)
Python에서는 원하는 객체에 "speak"신호를 보낼 수 있습니다. 객체가 처리 할 수 있으면 실행되고 그렇지 않으면 예외가 발생합니다. 두 코드 모두에 Airplane 클래스를 추가하고 makeSpeak에 Airplane 객체를 제출한다고 가정합니다. C ++의 경우 Airplane은 Animal의 파생 클래스가 아니기 때문에 컴파일되지 않습니다. Python의 경우 런타임에 예외가 발생하며 이는 예상되는 동작 일 수도 있습니다.
다른 쪽에서는 speak () 메서드를 사용하여 MouthOfTruth 클래스를 추가한다고 가정합니다. C ++의 경우 계층 구조를 리팩터링하거나 MouthOfTruth 객체를 허용하기 위해 다른 makeSpeak 메서드를 정의해야합니다. 또는 Java에서 동작을 CanSpeakIface로 추출하고 각각에 대한 인터페이스를 구현할 수 있습니다. 많은 해결책이 있습니다 ...
제가 지적하고 싶은 것은 아직 파이썬에서 상속을 사용하는 단 하나의 이유를 찾지 못했다는 것입니다 (프레임 워크와 예외 트리를 제외하고는 대체 전략이 존재한다고 생각합니다). 다형성을 수행하기 위해 기본 파생 계층을 구현할 필요가 없습니다. 상속을 사용하여 구현을 재사용하려는 경우 포함 및 위임을 통해 동일한 작업을 수행 할 수 있으며 런타임에 변경할 수있는 추가 이점이 있으며 의도하지 않은 부작용 위험없이 포함 된 인터페이스를 명확하게 정의 할 수 있습니다.
그래서 결국 질문이 있습니다. 파이썬에서 상속의 요점은 무엇입니까?
편집 : 매우 흥미로운 답변에 감사드립니다. 실제로 코드 재사용에 사용할 수 있지만 구현을 재사용 할 때는 항상주의해야합니다. 일반적으로 저는 매우 얕은 상속 트리를 수행하거나 트리를 전혀 사용하지 않는 경향이 있으며, 기능이 공통적이면 공통 모듈 루틴으로 리팩토링 한 다음 각 객체에서 호출합니다. 단일 변경점을 갖는 이점이 있습니다 (예 : Dog, Cat, Moose 등을 추가하는 대신 상속의 기본 이점 인 Animal에 추가합니다). 위임 체인 (예 : JavaScript). 나는 그것이 더 낫다고 주장하는 것이 아니라 단지 다른 방법입니다.