銀行員からのRailsエンジニア

銀行員からのRailsエンジニア

銀行員から転身したサービス作りが大好きなRailsエンジニアのブログです。個人で開発したサービスをいくつか運営しており、今も新しいサービスを開発しています。転職して日々感じていること、個人開発サービス運営のことなどを等身大で書いていきます。

【Rubyによるデザインパターンまとめ4】Compositeパターン

コードの品質向上のため、Rubyデザインパターンを解説した名著である Rubyによるデザインパターン で紹介されているデザインパターンを1つずつまとめており、今回が第4弾です。(毎週1つが目標です!)

前回の記事(オブザーバーパターンのまとめ)はこちらです。
ysk-pro.hatenablog.com

この本で紹介されているサンプルコードをそのまま使うのは面白くないので、オリジナルのコードで説明しています。

今回は Composite(コンポジット)パターン についてまとめました。

f:id:ysk_pro:20191117170819p:plain

Compositeパターンとは

あるものと、それが集まってできたものを同じように扱うことができるデザインパターンです。

例えば、会社は人が集まって課ができており、課が集まって部ができており、部が集まって会社ができています。人が会社から給料を受け取っているのと同じように、人の集まりである課や部も会社から給料を受け取っていると考えることができ、人・課・部が受け取っている給料を共通のインターフェースで扱うことができるイメージです。

Rubyによるデザインパターン のこちらの説明はわかりやすかったです。

GoF「全体が部分のように振る舞う」という状況を表すデザインパターンを、Compositeパターンと呼んでいます。Compositeパターンは、階層構造やツリー構造のオブジェクトを作りたいとき、そしてそのツリーを利用するコードが1つの単純なオブジェクトを扱っているのか、それともごちゃごちゃした枝全体を扱っているのかを考えさせたくないときに利用できます。

GoFとは、ギャング オブ フォーの略でデザインパターンを広めた「オブジェクト指向における再利用のためのデザインパターン」の4人の著者のことです)

親となるオブジェクトのメソッドを呼び出すことで、子オブジェクトの同じメソッドを再帰的に呼び出すケースでよく使われます。会社の例だと、部の給料を求めるメソッドを呼び出すことで、課・人の給料を求めるメソッドを呼び出し、それを集計して値を返すイメージです。

Composite(コンポジット)とは、「複合的」という意味の単語です。

Compositeパターンには3つの要素によって構成されています。

言葉での説明よりも実際のコードを見た方が分かりやすいと思うので、以下のサンプルコードをご覧ください。

サンプルコード

最近はもっぱらゴルフにハマっていて、ゴルフクラブセットが欲しいという気持ちが強いので、ゴルフクラブセットの金額を求めるプログラムを考えてみます。

ゴルフクラブセットは、ゴルフバッグと、ゴルフクラブ(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によるデザインパターン の中では、様々な例を用いて説明がされていると共に、この記事には書ききれなかった注意事項などが色々と書かれているので、ご興味ある方は是非合わせてご覧ください。

Rubyによるデザインパターン

Rubyによるデザインパターン

次回は、イテレータパターンをまとめます。

来週も頑張ります!

(追記)
イテレータパターンについてまとめました!
是非合わせてご覧ください。
ysk-pro.hatenablog.com