次のような状況があるとします。
#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 :: talk()またはDog :: think()のいずれのメソッドを呼び出すかを処理します。これは、makeSpeakに関する限り、どのサブクラスが実際に渡されるかについての知識は無関係であることを意味します。
しかし、Pythonはどうですか?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)
さて、この例では同じ戦略が見られます。継承を使用して、犬と猫の両方が動物であるという階層的な概念を活用します。しかし、Pythonでは、この階層は必要ありません。これは同じようにうまく機能します
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を追加し、AirplaneオブジェクトをmakeSpeakに送信するとします。C ++の場合、AirplaneはAnimalの派生クラスではないため、コンパイルされません。Pythonの場合、実行時に例外が発生しますが、これは予想される動作である可能性もあります。
一方、speak()メソッドを使用してMouthOfTruthクラスを追加するとします。C ++の場合、階層をリファクタリングするか、MouthOfTruthオブジェクトを受け入れるために別のmakeSpeakメソッドを定義する必要があります。または、Javaでは、動作をCanSpeakIfaceに抽出して、それぞれのインターフェイスを実装できます。多くの解決策があります...
私が指摘したいのは、Pythonで継承を使用する理由はまだ1つも見つからないということです(フレームワークと例外のツリーは別として、別の戦略が存在すると思います)。多態的に実行するために、ベースから派生した階層を実装する必要はありません。継承を使用して実装を再利用する場合は、包含と委任を通じて同じことを実現できますが、実行時に変更できるという追加の利点があり、意図しない副作用のリスクを冒すことなく、包含のインターフェイスを明確に定義できます。
それで、結局のところ、問題は立っています:Pythonの継承のポイントは何ですか?
編集:非常に興味深い答えをありがとう。確かにコードの再利用に使用できますが、実装を再利用するときは常に注意が必要です。一般に、私は非常に浅い継承ツリーを実行するか、ツリーをまったく実行しない傾向があります。機能が共通している場合は、共通のモジュールルーチンとしてリファクタリングし、各オブジェクトから呼び出します。単一の変更点を持つことの利点はわかりますが(たとえば、犬、猫、ムースなどに追加する代わりに、継承の基本的な利点である動物に追加するだけです)、同じことを達成できます。委任チェーン(例:JavaScript)。私はそれがより良いとは言いませんが、ただ別の方法です。