Tbpgr Blog

Ruby プログラマ tbpgr(てぃーびー) のブログ

Ruby | Composite Pattern

概要

Composite Pattern

詳細

Composite Patternは、全体が部分のように振る舞えるようにするパターンです。
階層やツリー構造を作り、利用者からは全体なのかツリーなのか意識しないで使えるように
設計したい場合に利用します。

サンプル仕様

マインドマップを表すクラスを作成します。
子を持たない要素を葉 : Leaf
子を持つ要素を根 : Root
とします。
葉、および根は名称を持ちます。
根は複数の葉を持つことができます。
本当はもっといろんな要素があるのですが、今回はシンプルに以上の要件にします。

上記のマインドマップを表すRoot, Leafを作成したうえで
再帰で各根・葉の名前を全表示するメソッドを作成します。

Compositeパターンサンプル

require 'tbpgr_utils'
require 'attributes_initializable'
require 'pp'

module Mindmap
  class Leaf
    include AttributesInitializable
    attr_reader_init :name
  end

  class Root < Leaf
    include Enumerable
    attr_reader :leaves

    def initialize(name, leaves = [])
      super(name)
      @leaves = leaves
    end

    def << (leaf)
      @leaves << leaf
    end

    def each
      @leaves.each { |leaf| yield(leaf) }
    end
  end
end

root = Mindmap::Root.new(name: "デザインパターン")
gof = Mindmap::Root.new(name: "GoF")
%w{エーリヒ・ガンマ リチャード・ヘルム ラルフ・ジョンソン ジョン・ブリシディース}.each do |member|
  gof << Mindmap::Leaf.new(name: member)
end

categories = Mindmap::Root.new(name: "パターン分類")
%w{生成 構造 振る舞い}.each { |category|categories << Mindmap::Leaf.new(name: category) } 

root << gof
root << categories

def show_maps(leaves, nested_level)
  tabs = "\t"*nested_level
  puts "#{tabs}#{leaves.name}"
  leaves.each do |leaf|
    if leaf.is_a? Mindmap::Root
      show_maps(leaf, nested_level + 1)
    else
      tabs = "\t"*(nested_level + 1)
      puts "#{tabs}#{leaf.name}"
    end
  end
end

show_maps(root, 0)


__END__
AttributesInitializableはtbpgr_utils gemの機能です。
詳しくは
https://github.com/tbpgr/tbpgr_utils
を参照。

出力

デザインパターン
	GoF
		エーリヒ・ガンマ
		リチャード・ヘルム
		ラルフ・ジョンソン
		ジョン・ブリシディース
	パターン分類
		生成
		構造
		振る舞い

Compositeパターンサンプル(変形)

最初のサンプルだと、再帰の処理にif文が入ってしまっています。
なので葉にeachメソッドを定義して空配列を返却することで、
根がeachで空配列を返却するのと同じ動作をさせることにします。
これにより、再帰に分岐がなくなります。

require 'attributes_initializable'
require 'pp'

module Mindmap
  class Leaf
    include AttributesInitializable
    attr_reader_init :name

    def each
      []
    end
  end

  class Root < Leaf
    include Enumerable
    attr_reader :leaves

    def initialize(name, leaves = [])
      super(name)
      @leaves = leaves
    end

    def << (leaf)
      @leaves << leaf
    end

    def each
      @leaves.each { |leaf| yield(leaf) }
    end
  end
end

root = Mindmap::Root.new(name: "デザインパターン")
gof = Mindmap::Root.new(name: "GoF")
%w{エーリヒ・ガンマ リチャード・ヘルム ラルフ・ジョンソン ジョン・ブリシディース}.each do |member|
  gof << Mindmap::Leaf.new(name: member)
end

categories = Mindmap::Root.new(name: "パターン分類")
%w{生成 構造 振る舞い}.each { |category|categories << Mindmap::Leaf.new(name: category) } 

root << gof
root << categories

def show_maps(leaves, nested_level)
  tabs = "\t"*nested_level
  puts "#{tabs}#{leaves.name}"
  leaves.each { |leaf|show_maps(leaf, nested_level + 1) }
end

show_maps(root, 0)

__END__
AttributesInitializableはtbpgr_utils gemの機能です。
詳しくは
https://github.com/tbpgr/tbpgr_utils
を参照。

出力

デザインパターン
	GoF
		エーリヒ・ガンマ
		リチャード・ヘルム
		ラルフ・ジョンソン
		ジョン・ブリシディース
	パターン分類
		生成
		構造
		振る舞い