コードの品質向上のため、Rubyでデザインパターンを解説した名著である Rubyによるデザインパターン で紹介されているデザインパターンを1つずつまとめており、今回が第4弾です。(毎週1つが目標です!)
前回の記事(オブザーバーパターンのまとめ)はこちらです。
ysk-pro.hatenablog.com
この本で紹介されているサンプルコードをそのまま使うのは面白くないので、オリジナルのコードで説明しています。
今回は Composite(コンポジット)パターン についてまとめました。
Compositeパターンとは
あるものと、それが集まってできたものを同じように扱うことができるデザインパターンです。
例えば、会社は人が集まって課ができており、課が集まって部ができており、部が集まって会社ができています。人が会社から給料を受け取っているのと同じように、人の集まりである課や部も会社から給料を受け取っていると考えることができ、人・課・部が受け取っている給料を共通のインターフェースで扱うことができるイメージです。
Rubyによるデザインパターン のこちらの説明はわかりやすかったです。
GoFは「全体が部分のように振る舞う」という状況を表すデザインパターンを、Compositeパターンと呼んでいます。Compositeパターンは、階層構造やツリー構造のオブジェクトを作りたいとき、そしてそのツリーを利用するコードが1つの単純なオブジェクトを扱っているのか、それともごちゃごちゃした枝全体を扱っているのかを考えさせたくないときに利用できます。
(GoFとは、ギャング オブ フォーの略でデザインパターンを広めた「オブジェクト指向における再利用のためのデザインパターン」の4人の著者のことです)
親となるオブジェクトのメソッドを呼び出すことで、子オブジェクトの同じメソッドを再帰的に呼び出すケースでよく使われます。会社の例だと、部の給料を求めるメソッドを呼び出すことで、課・人の給料を求めるメソッドを呼び出し、それを集計して値を返すイメージです。
Composite(コンポジット)とは、「複合的」という意味の単語です。
Compositeパターンには3つの要素によって構成されています。
- コンポーネント(Component)クラス:全てのオブジェクトの共通のインターフェイス
- 葉(Leaf)クラス:子を持たない。会社の例では人にあたる。Componentインターフェイスを実装している
- コンポジット(Composite)クラス:子を持つ。会社の例では課・部にあたる。Componentインターフェイスを実装している
言葉での説明よりも実際のコードを見た方が分かりやすいと思うので、以下のサンプルコードをご覧ください。
サンプルコード
最近はもっぱらゴルフにハマっていて、ゴルフクラブセットが欲しいという気持ちが強いので、ゴルフクラブセットの金額を求めるプログラムを考えてみます。
ゴルフクラブセットは、ゴルフバッグと、ゴルフクラブ(10数本)で構成されています。さらにゴルフクラブは、ドライバー、アイアン(パターもあるけど今回は省略)で構成されています。
ゴルフクラブセットの金額を求めるには、それぞれの金額を全て足していく必要があります。
これをCompositeパターンで実装してみます。
# Componentクラス class Part attr_reader :name def initialize(name) @name = name end def price raise # インターフェイスとなるメソッドであり、呼び出されたらエラーを返すようにしている end end # Compositeクラスの基底クラス class CompositePart < Part def initialize(name) super(name) @sub_parts = [] end def add_sub_part(part) @sub_parts << part end def remove_sub_part(part) @sub_parts.delete(part) end def price @sub_parts.map(&:price).inject(:+) # 子クラスのpriceを集計 end end # Compositeクラス class ClubSet < CompositePart def initialize super('クラブセット') add_sub_part(Club.new) add_sub_part(GolfBag.new) end end # Leafクラス class GolfBag < Part def initialize super('ゴルフバッグ') end def price 10_000 end end # Compositeクラス class Club < CompositePart def initialize super('クラブ') add_sub_part(Driver.new) add_sub_part(Iron.new) end end # Leafクラス class Driver < Part def initialize super('ドライバー') end def price 30_000 end end # Leafクラス class Iron < Part def initialize super('アイアン') end def price 15_000 end end # -- 実行 -- p ClubSet.new.price # -> 55000
ClubSetクラスのインスタンスのpriceメソッドを呼ぶと、Compositeクラス・Leafクラスのpriceメソッドを呼んで金額を足していき、合計金額を出力できています。
ゴルフは楽しいけど、お金がかかりますね。
おわりに
ここまで読んでいただきありがとうございます。
個人的に前回までのデザインパターンよりもイメージが湧きづらく、少し自信がない部分もある(今回の記事はかなり時間がかかった...)ので、ここちょっと違うんじゃないの、であったり、ここをこうするともっと分かりやすいよ、みたいなことがあればお気軽にコメントいただけると嬉しいです。
Rubyによるデザインパターン の中では、様々な例を用いて説明がされていると共に、この記事には書ききれなかった注意事項などが色々と書かれているので、ご興味ある方は是非合わせてご覧ください。
- 作者: Russ Olsen,ラス・オルセン,小林健一,菅野裕,吉野雅人,山岸夢人,小島努
- 出版社/メーカー: ピアソン桐原
- 発売日: 2009/04/01
- メディア: 単行本
- 購入: 13人 クリック: 220回
- この商品を含むブログ (66件) を見る
次回は、イテレータパターンをまとめます。
来週も頑張ります!
(追記)
イテレータパターンについてまとめました!
是非合わせてご覧ください。
ysk-pro.hatenablog.com