オブジェクト指向再入門

投稿者: | 2012年5月24日

はじめに

オブジェクト指向について、基本的な概念を再確認、ということを説明していこうと思います。

開放-閉鎖原則

まず、オブジェクト指向以前に、理解しておく概念がありました。それが「開放-閉鎖原則」です。

モジュールは、拡張に対して開いており、変更に対して閉じているべきである
(注:オブジェクト指向においては、モジュール=クラスとなります)

これは、Eiffelという言語の考案者のバートランド・メイヤーさんが、提唱しているものらしいです。これは、ソフトフェア開発についてはもちろん、オブジェクト指向・デザインパターンを理解するうえで、重要な考え方ですね。

そんな「開放-閉鎖原則」をぶっちゃけていうと、、モジュールに、機能を追加するのはOKですが、機能そのものを変更するのはNGということです。機能変更を行うと、他のモジュールを修正したり、それまでのテストをやり直す必要が出てくるなど、修正による影響範囲が大きいです。

それに対して機能追加は、予想される範囲であれば、設計時に「修正に対応できるような作り」にすることで、他への影響を最小限にとどめることが可能です。

オブジェクト指向における「修正に対応できるような作り」というものが、「継承(インヘリタンス)・委譲(デリゲーション)」なのです。

 

継承と委譲の違い

「継承・委譲」という言葉が出たところで、ちょっと寄り道して、この違いについて、説明しておきます。

まず、継承には以下の特徴があります。

  • 継承元と静的なつながりができる
  • つながりが分かり易い
  • シンプル
  • (多重継承が認められていなければ)単一のクラスとしかつながりがない
  • その場合に、機能を増やすたびにクラスがどんどん増えていく
  • 静的なつながりなので、オブジェクトの置き換え・共有という概念はない
  • つながりに柔軟性がない

シンプルだが柔軟性がない、ということがポイントですね。

それに対して委譲です。委譲について簡単に説明すると、クラスのメンバ変数(フィールド)に別のクラスの参照を持ち、そのクラスの機能を使うことができるようにする。ということです。

で、委譲の特徴です。

  • 委譲先を実行時に動的に変えることができる
  • つながりが分かりにくい
  • 複雑になりやすい
  • 複数のクラスとつながることができる
  • 機能が増えてもクラスが増えるとは限らない
  • 自由にインスタンスを置き換え・共有できる
  • 柔軟性がある

複雑だが柔軟性がある、ということがポイントです。

ということで、機能追加を行う場合に、単純に「継承」を使ってしまうケースがよくあるみたいですが、それぞれの特性を、よく考えて使うのがポイントですね。

例)

  • 継承のパターン:ユニット(基底クラス)を継承して、戦士・魔法使い・僧侶を作る
  • 委譲のパターン:戦士に武器・防具・属性を持たせる

継承が、「戦士はユニットである」ことから、「is-a関係」

委譲が、「戦士は武器を持っている」ことから、「has-a関係」

と言ったりします。

で、なんなの?

ちょっと、横道にそれてしまいましたが、オブジェクト指向の目的は、「機能追加」を容易に行う仕組みのことです。

例えば先ほどの例ですと、

「ユニットに、狩人を追加したい」

といった要望が出た場合には、ユニットを継承して狩人クラスを作り、

ユニット生成管理クラスで、狩人の生成処理を追加するだけでよくなります。

 

ただ、逆に言うと、「機能追加」がなければオブジェクト指向を使う必要がないわけです。

 

例えば、「プレイヤーは戦士と魔法使いと僧侶だけ」という要件がキッチリ固まっていれば(プレイヤーキャラの追加を認めない)、基底となるユニットクラスを用意するのは、無駄になります。

…さらに、オブジェクト指向で書くと、ソースコードの記述量が増えたり、設計が複雑になってしまいます。(先ほどの例では、クラスが余分に増えてますよね)

つまり、なんでもかんでもオブジェクト指向で書けばいいというものではないのです。

自分が作ろうとしているものをよく考えてみて、機能が追加されるだろう、と思う部分にはオブジェクト指向の考えを入れて、それ以外は、シンプルな設計にするべきなのです。この考え方は、「デザインパターン」にも言えます。

 

インターフェースの抽出

ちょっと、補足です。

オブジェクト指向の肝に、インタフェースというものがあります。インタフェースとは、外部にどんな機能を提供するのか、内部でどんな機能を実装するのか、というものを定義したものです。

入れ物はあるけど中身が空っぽ、というイメージで、継承して使うことを前提としています。

では、その入れ物をどのように記述するべきかというと、「機能追加がありそうな部分」ということになります。

単に、ソースコードを見て、似たようなクラスがあるから、「ここは、インタフェースで共通化したほうがよくね?」という杓子定規で設計するのではなく、「ここは機能追加があるから、インタフェースにするんだ!」という機能的な要件がある場合に、インタフェースを抽出するのが、インタフェースの正しい使い方なのです。

参考リンク集