例えば、if〜instanceofを避ける(1)

先日の地豆の開発チャットでの話題をまとめておく。

Javaにおいて、気をつけて使わないとオブジェクト指向の世界を大きく壊してしまう可能性のある危険ワードはstaticとinstanceofだと思っている。staticについては、継承とコンポジットで少しだけ触れた通り。今日はinstanceofについて。

先日、オブジェクト指向は「隠す」技術だと言ったが、今日は「まとめる」技術だと言ってみる。

  • class A extends X
  • class B extends X


だったとして、何も考えずにこんなコードを書いたとする。

X foo = ...;

if (foo instanceof A) {
  // class A用の処理(a)
} else if (foo instanceof B) {
  // class B用の処理(b)
}

見た感じ思うのはこんなこと。とても危険な臭いがするコードだ。

  • Xのサブクラスとして、Cが増えた時、C用の処理を書き忘れそうだ。
  • 同じ処理があちこちのクラスに複数回現れたら、コピペしそうだ。

オブジェクト指向的に、一般的にどう対処するとよいのか。早速だが解答編いっちゃう。ポリモーフィズムを使えばいい。

class X {
  public abstract void process();
}

class A extends X {
  public void process() {
    // (a)
  }
}

class B extends X {
  public void process() {
    // (b)
  }
}
X foo = ...;
foo.process();

こうすれば、新しくXのサブクラスCを作った時もprocessの実装を忘れることはないし、processの処理がどこで現れても、単にprocess()を呼び出せば良い。さらに、煩雑なif文を書かずに済むというのは、プログラムの複雑性を下げ、バグが潜む可能性を減らしてくれる。if文の判定ミスでバグ、なんてよくある話だもんね。

結果、処理a/bをそれぞれのクラスと紐づけて、管理しやすくなる。これが「まとめる」。凝集度が上がったと考えられる。

instanceofというのは、せっかく抽象化している(インスタンスを、AやBではなく、それよりも抽象的なXとして扱っている)のに、具体を引っ張り出してきてしまう。「抽象的に扱う」のは「より隠された*1状態で扱う」のと同じだ。メソッド数が少ない訳だから。instanceofを使うと、隠せるものを「見せて」しまう。

でだ。全てがこのやり方で解決できればよいのだが、静的言語ではとある限界が存在する。

XがObject、AがList、BがMapだったらどうだろう。この解決方法は、各クラスにprocessメソッド*2を作らなければならないのだが、ListとMapとObjectって、自分でメソッド作れないじぇ? というw

これは明日書くことにする。寝るw

*1:「隠す」に関しては「継承とコンポジット」参照。

*2:名前は適当