idkqh7の研究日誌

基本は翻訳。{セキュリティ|プログラミング|バイオインフォマティクス|数学}について。

【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]

参考文献