Skip to main content
オブジェクト指向の「コンポジション(Composition)」とは、あるクラスが他のクラスのインスタンスを内部に保持することで複雑な機能を実現する設計方法のことです。「継承(Inheritance)」と並ぶ、オブジェクト指向設計における重要な考え方の一つです。

なぜコンポジションを使うのか?

オブジェクト指向では、「継承」と「コンポジション」という 2 つの方法でクラス間の関係性を表現できます。
  • 継承(Inheritance) は、「is-a」の関係(〜は〜である)を表現するのに向いています。 例:「犬は動物である(Dog is an Animal)」
  • コンポジション(Composition) は、「has-a」の関係(〜は〜を持っている)を表現するのに向いています。 例:「車はエンジンを持っている(Car has an Engine)」
コンポジションを使うと、クラス間の結合度(依存関係)が低くなり、設計が柔軟になります。これは、プログラムの拡張性や保守性を高めるために重要です。

Ruby によるコンポジションの具体例

具体的な例として、Ruby で「車(Car)」クラスと「エンジン(Engine)」クラスの関係をコンポジションで表現してみましょう。

1. エンジンクラス(Engine)を作成する

2. 車クラス(Car)を作成し、Engine のインスタンスを持つ

3. インスタンスを生成して実行する


なぜ継承ではなくコンポジションを使うのか?

「車」と「エンジン」の関係を継承で表現するのは不自然です。車はエンジンではない(Car is not an Engine)からです。 一方、コンポジションを使えば、「車はエンジンを持っている(Car has an Engine)」という自然な関係性を綺麗に表現できます。

アンチパターン:継承の誤用

以下は継承を誤って使用した例です:
この例では、Car が Engine を継承していますが、これは「車はエンジンである」という誤った関係を表現しています。 さらに、コンポジションの大きな利点は「部品の交換が簡単」なことです。例えば、ガソリンエンジンから電気エンジンに変更したくなった場合、以下のように柔軟に対応できます。

依存性注入(Dependency Injection)

上記の例では、エンジンを外部から注入する「依存性注入」というパターンを使用しています。これにより、Car クラスは特定のエンジンの実装に依存せず、任意のエンジンを使用できるようになります。

より複雑なコンポジションの例

実際のアプリケーションでは、クラスは複数のコンポーネントを持つことが一般的です。車の例をさらに拡張してみましょう。
このように、コンポジションを使うことで、各部品(エンジン、タイヤ、ブレーキ、ナビゲーション)を独立して管理でき、必要に応じて組み合わせを変更できます。

コンポジションのメリットまとめ

  • 柔軟性の向上:クラスの振る舞いを組み合わせたり変更したりしやすい。
  • 保守性の向上:各クラスが独立していて、変更が他のクラスに影響しにくい。
  • 低い結合度(Low Coupling):クラス同士が疎結合となり、コードの再利用性が高まる。

練習問題

問題 1: 基本的なコンポジション

以下の要件を満たす Computer クラスと関連するコンポーネントクラスを実装してください。 要件:
  • Computer は CPU、Memory、Storage を持つ
  • 各コンポーネントは info メソッドを持ち、自身の仕様を返す
  • Computer クラスは show_specs メソッドで全コンポーネントの情報を表示する

問題 2: 柔軟な設計

音楽プレーヤーアプリケーションを設計してください。 要件:
  • MusicPlayer クラスは、AudioDecoder と Speaker を持つ
  • AudioDecoder は MP3Decoder と FLACDecoder の 2 種類がある
  • Speaker は StandardSpeaker と HighQualitySpeaker の 2 種類がある
  • 依存性注入を使って、任意の組み合わせで MusicPlayer を作成できるようにする

解答例


まとめ

オブジェクト指向のコンポジションとは、「has-a」の関係を表現する設計手法であり、Ruby においてもシンプルかつ柔軟なコードを実現するための非常に効果的な方法です。 Ruby のような動的な言語では、特にコンポジションを活用することで、拡張性やメンテナンス性が高いコードを書くことができます。