最近、Pythonでコードを書いていて、薄々「継承で使うのはmix-inクラスだけでいいんじゃないか」と感じています。ここでいうmix-inクラスというのは、以下の記事で述べられているような「インスタンス変数を含まず、メソッドのみを持つ多重継承用のクラス」のことです。
あるいは、「インスタンス変数を含むクラスを継承するのは1つだけ」とするルールを自らに課す方法もある。その場合、追加で継承できるのは「メソッドのみを含んだクラス」となる。そうすることで、インスタンス変数の共有によって生じる問題を回避できるようになる。
これに反論のある方は少ないと思います。また、「親クラスでインスタンス変数を定義する処理(ほぼ __init__
だと思います)を共通化する必要性がなければ、通常の親クラスを使わずにmix-inクラスだけで表現できるのではないか?」さらに「 __init__
などの処理を共通化して得られるコストメリットは少ないのではないか?」と感じています。その理由は以下の通りです。
- Rustなどの言語では「インスタンス変数を含まない多重継承用のクラス」にあたるtraitと実装用の構造体の機能がはっきり分かれている(詳しくないので間違ってたら教えてください)
- 「インスタンス変数を利用する処理を複数の子クラスで共通化する」ことにメリットがあるコードに出くわしたことがないように感じている
- 他に自分が感じている感想として、単一継承でも継承関係が深くなると、インスタンス変数がどこで定義されているものか分からなくなる。そもそもリスコフの置換原則を満たしていないクラスに継承を使っているようなコードだったのが問題かもしれませんが…
この話を友人にしたところ、「多重継承したい場合に『インスタンス変数を含むクラスを継承するのは1つだけ』とするルールが必要なのは異論ない」「ただ、 __init__
の処理を共通化するメリットがある場面もあるのではないか?(極端な例だが子クラスが100個あって全部で同じ初期化処理がある場合)」というような意見を貰いました。
というわけで、「mix-inだけでいいんじゃないか説」の完全な証明にはならないものの、「子クラスが100個あって全部で同じ初期化処理がある場合」に、「インスタンス変数を含まない多重継承用のクラス」しかない言語ではどう解決しているのか(それとも __init__
を共通化するより楽な設計はあるのか)を調べてみようと思います。
と思ったのですが、サクッと調べた程度では全然結論が出なかったので一旦諦めました。
様々な記事
class Dog include Runnable end class Cat include Runnable end module Rannable def run end end Dog.new.run
こちらも「Dogは走れるものの一種(Dog is a Runable)」と解釈できるんじゃないかと思ってしまいます。
TODO:: Rustのtraitが導入されるまでの議論を追う
TODO:: 「inheritance is not subtyping」問題