継承やめろマジやめろ。な
ぜイケないのか
解説する
2021/03/31
第1事業本部第1開発部プログラマー山田大志
経歴
・普通のITシステム会社
・ゲームスタジオ
趣味
・最近はLoopHeroというゲームをやったりやらなかったり。
出身
・鹿児島
継承好きですか?
私は嫌いです
プログラム的にも
良くないことが多い
しかし、どうして
良くないのか
わかっていない人も多い
解説します
そもそも継承とは
どうあるべきか?
is-a関係(継承関係)
文字通り、is-aの関係。A is a B.
AはBの一種。
つまり、サブクラスBはスーパークラスAの一種でなければいけないです。
似たようなものにhas-a関係(包含関係)があります。A has a B.
AはBを含む。
これはフィールドに変数を持っている状態のことです。
コンポーネント(部品)と呼んだりします。
リスコフの置換原則
継承したクラスは、継承元クラスと同じ動作をしなければならない。
簡単にいうと、AのスーパークラスをBのサブクラスで置き換えても
挙動が変わらないようにすることです。
理由はサブクラスBで動作を置き換えてしまった時に
Aのとき実装した挙動が変わってしまいます。
(SOLID原則の単一責任の原則に引っかかる
→後で軽く解説します)
また変更が加わっていること自体が分かりづらくなってしまうからです。
一見満たしてれば
使っても良さそうに見える
じゃあ、なぜ継承が
よくないのか?
そもそもis-aを満たすオブジェクトが少ない
さきほど解説したis-aの関係一見簡単そうにみえます。
ちょっと、質問です。
柴犬は犬の一種ですか?
柴犬は哺乳類の一種ですか?
柴犬は生命体の一種ですか?
〇〇の一種というのは、そもそもデカすぎ
先程の例は極端ですが、〇〇の一種という言葉は意外と曖昧です。
サブクラスがスーパークラスの一種という言葉をつかえても
設計においてis-a関係を満たすわけではない。
「サブクラスがスーパークラスの一種」という言葉自体がプログラムにおいて意味合いが広すぎる場合が多
い。
設計において、ちゃんと「サブクラスがスーパークラスの一種」と言い表せる例は殆どない。
具体的にどう良くないのか?
よくある設計
なんにも職業を持たない基本的なキャラクターを基底クラスにして
職業を持つキャラクターを実装するとします。
class Character{
int Defaultパワー = 0;
void SetDefaultパワー(int パワー){
Defaultパワー = パワー;
}
int Fight(){
return Defaultパワー;
}
}
class SwordFighter:Character{
int Defaultパワー = 0;
int パワー倍率 = 1;
void SetDefaultパワー(int パワー){
Defaultパワー = パワー;
}
void Setパワー倍率(int パワー){
パワー倍率 = パワー;
}
int Fight(){
return Defaultパワー * パワー倍率;
}
}
ディレクター
「転職システム入れよう」
class Character{
int Defaultパワー = 0;
void SetDefaultパワー(int パワー){
Defaultパワー = パワー;
}
int Fight(){
return Defaultパワー;
}
}
class SwordFighter:Character{
int Defaultパワー = 0;
int パワー倍率 = 1;
void SetDefaultパワー(int パワー){
Defaultパワー = パワー;
}
void Setパワー倍率(int パワー){
パワー倍率 = パワー;
}
int Fight(){
return Defaultパワー * パワー倍率;
}
}
プログラマー
「どこから手を付ければ……」
という感じに
  困ってしまいます……。
じゃあ、
どうすりゃいいのか
継承より委譲(合成)
Favor composition over
inheritance
処理を委譲する
処理は自身継承するのではなく、
Componentとして委譲してしまう。
class Character{
I職業クラス職業;
int Fight(){
return 職業.パワー;
}
}
こうすることで、サブ職業を追加して欲しいと言われる場合でもワリと簡単に対応が出来る。
特に基底クラスを作る理由が処理の共通化の場合は基底クラスを作らずに
Serviceクラスとしてしまって、処理を含むような形で持ってしまったほうが良いです。
反論
「Componentだらけになりそう」
アンサー
「Componentだらけになる時は
そもそも設計がおかしいので
見直しましょう」
責務が大きすぎる
プログラムにおいて理想とされるのは単一責任の原則と言って、
プログラムが変更される理由は一つであるべきというのがあります。
いわゆる変更があらゆるクラスに波及してしまうのは良くないということです。
Componentを大量に抱えたクラスはそれぞれの
Componentが変更される際に変更する必要がないのにその
クラスに対して変更が入ってしまいます。
その場合はそのクラスが持つ責務
(ドメインとも言ったりする)が大きすぎます。
クラスを分割しましょう。
まとめ
まとめ
言いたいことは一つ!
処理を共通化したい場合は、継承ではなく、合成。
処理を委譲するようにしましょう!
以上です

継承やめろマジやめろ。 なぜイケないのか 解説する