【Go言語】スーパークラスのget()からサブクラスのメソッドをオーバーライドして使うときの話【Golang】
結論
interfaceを使いましょう!!!
サブクラスのget()を呼び出したとき、出力はどうなるか? (Python)
Pythonで以下のような継承をしたと仮定する。
スーパークラスのget()からサブクラスのメソッドをオーバーライドして呼び出している。
[Class]ー[get]ー[Class: make_string] | | [SubClass]ー[SubClass: make_string]
このとき、SubClassのgetをよびだすと
[SubClass]ー[get]ー[SubClass: make_string]
と呼び出される。
当然、実行結果も以下のようになる。
Hi, Super! Hello, Sub!
サブクラスのget()を呼び出したとき、出力はどうなるか? (Go)
※Goにクラスはないが、structだとかFactory関数だとかそんな感じのヤツを便宜上クラスと呼ぶことにする。
実行結果は以下のようになる。
Hi, Super! Hi, Sub!
SubClassのgetをよびだしても
[SubClass]ー[get]ー[Class: make_string]
となり同じ結果にならないことが分かる。
さきほどの図を見ながら、呼び出しの関係をもう一度考えてみよう。
【Pythonの場合】
[Class]ー[get]ー[Class make_string] → make_stringがSubClassでオーバーライド → get()の中身を変更 → get[SubClass: make_string] ↑ ↑ [SubClass]ー[SubClass: make_string] → スーパークラスのget()を呼び出す → get()オーバーライド成功
【Goの場合】
[Class]ー[get]ー[Class make_string] → get()の中身はコンパイル時に確定 → get()の中身を変更できない → get[Class: make_string] ↑ 〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜〜 ↑ [SubClass]ー[SubClass: make_string] → スーパークラスのgetを呼び出す → get()オーバーライド失敗(そもそもオーバーライドしてない)
つまり、Pythonの場合は暗黙的にオーバーライドされるが、Goの場合は明示的にしかオーバーライドされない。
インタプリタ方式は実行時に依存関係を解析できるので、動的に書き換えていくような実行に強い。 しかし、コンパイル方式では依存関係を実行前に解決する必要があるため、このような書き方を推奨することができない。 (Goだけでなく、コンパイル方式ではよくある話)
解決法: Interfaceを使う。
要は、呼び出してみるまで「メソッドの中身が分からない」という設計は、コンパイル方式のやり方ではイケてないということだ。 メソッドの中身が分からないのであれば、メソッドの中身を明示してあげれば良い。
そこで、呼び出し元を実装クラスに一元化し「◯◯のクラス使うよー」と言う仕組みを考える。
[ImplClass]ー[get]ー[Interface: make_string] [Class]ー[make_string] | | [SubClass]ー[make_string]
get()を呼び出すときはImplClassから呼び出し、ImplClassでどのクラスからmake_stringを呼びだすか決める。 このような仕組みであれば、ImplClassに向かって「SubClass使うよー」といってあげれば
[ImplClass: SubClass]ー[get]ー[Interface: make_string]ー[SubClass: make_string]
という経路をたどるので、コンパイル方式でも実行前に依存関係が自明となる。
実行結果は以下のようになる。
Hi, Super! Hello, Sub!
逆説的に言うと、「引数にクラスを指定することで、呼び出すメソッドの内容が書き換わるメソッド」を自作するときには、たぶんこんな感じになるはず……。
楽しいGo生活を!
結論:interfaceを使いましょう!!(大事なことなのでry)
おまけ: Factory関数使わない版
補足
Strategyパターン?
見返したらまさしくそうなってた。
client-specified self patternとかどうよ?
よく分かってない可能性もあるが、ごちゃごちゃするのであまり使いたくない(分かりやすい方でやればOK)
ここでいうImple構造体をSuper構造体で参照(継承っぽく)しても大丈夫?
やめたほうが良いと思う。あくまでImple構造体は呼び出しのためのワンクッション。
[ImplClass]ー[get]ー[Interface: make_string] ↑ ImplClassが直接依存関係にないのがミソ ↓ [Class]ー[make_string] | | [SubClass]ー[make_string]