酒と泪とRubyとRailsと

Ruby on Rails と Objective-C は酒の肴です!

コンポジット Ruby 2.0.0 デザインパターン速攻習得[Composite][Design Pattern]

GoFのデザインパターン(Design Pattern)のコンポジット(Composite )のRubyコードを使った紹介記事です。


コンポジットとは?

「全体-部分」(個別のオブジェクトと合成したオブジェクト)を同一のものとして捉えることで、再帰的な構造をクラスで表現することをCompositeデザインパターンと呼びます。「全体-部分」は同じインターフェイスを継承します。

コンポジットは次の3つの要素によって構成されます。

コンポーネント(Component):すべてのオブジェクトの基底となるクラス
リーフ(Leaf):プロセスの単純な構成要素、再帰しない
コンポジット(Composite):コンポーネントの一つでサブコンポーネントで構成

例としては、ディレクトリとフォルダを同様のコンポーネントとして扱うことで、削除処理などを再帰的に行えるようにできることが挙げられます。

ちなみに「再帰」とは、ある処理の中で再びその手続きを呼び出すことです。

コンポジットのメリット

ファイルシステムなどの木構造を伴う再帰的なデータ構造を表現できる
階層構造で表現されるオブジェクトの取扱いを楽にする

ソースコード

ここではファイルシステムのモデルを使ってCompositeデザインパターンを説明します。

FileEntryクラス(Leaf):ファイルを表す
DirEntryクラス(Composite):ディレクトリを表す
Entryクラス(Component):FileEntry, DirEntryクラスの共通メソッドを規定

まずFileEntryクラス, DirEntryクラスの共通メソッドを規定するEntryクラスです。Componentにあたります。メソッドの実装はFileEntry/DirEntryクラスが個別に持っています。

1
2
3
4
5
6
7
8
9
10
11
# FileEntry, DirEntryクラスの共通メソッドを規定(Component)
class Entry
  # ファイル/ディレクトリの名称を返す
  def get_name; end

  # ファイル/ディレクトリのパスを返す
  def ls_entry(prefix) end

  # ファイル/ディレクトリの削除を行う
  def remove; end
end

次にファイルを表すFileEntryクラスです。ファイルはその下にファイルを持つことができない、つまり再帰できないのでCompositeデザインパターンのLeafにあたります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# Leaf (中身)
class FileEntry < Entry
  def initialize(name)
    @name = name
  end

  # ファイルの名称を返す
  def get_name
    @name
  end

  # ファイルのパスを返す
  def ls_entry(prefix)
    puts(prefix + "/" + get_name)
  end

  # ファイルの削除を行う
  def remove
    puts @name + "を削除しました"
  end
end

最後がディレクトリを表すDirEntryクラスです。ディレクトリはその下にファイルを持つことができる、つまり再帰できるのでCompositeデザインパターンのCompositeにあたります。このクラスは独自のクラスとして、ファイルを追加する#addメソッドを持っています。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# Composite
class DirEntry < Entry
  def initialize(name)
    @name = name
    @directory = Array.new
  end

  # ディレクトリの名称を返す
  def get_name
    @name
  end

  # ディレクトリにディレクトリ/ファイルを追加する
  def add(entry)
    @directory.push(entry)
  end

  # ファイル/ディレクトリのパスを返す
  def ls_entry(prefix)
    puts(prefix + "/" + get_name)
    @directory.each do |e|
      e.ls_entry(prefix + "/" + @name)
    end
  end

  # ファイル/ディレクトリの削除を行う
  def remove
    @directory.each do |i|
      i.remove
    end
    puts @name + "を削除しました"
  end
end

コーディングは以上です。結果を確認します。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
root = DirEntry.new("root")
tmp = DirEntry.new("tmp")
tmp.add(FileEntry.new("conf"))
tmp.add(FileEntry.new("data"))
root.add(tmp)

root.ls_entry("")
#/root
#/root/tmp
#/root/tmp/conf
#/root/tmp/data

root.remove
#confを削除しました
#dataを削除しました
#tmpを削除しました
#rootを削除しました

ディレクトリの中にディレクトリ/ファイルを追加できて、#ls_entryメソッド#removeメソッドで同一のものとみなして処理できていることがわかります。 (再帰的にメソッドを呼び出せていることがわかります)

このサンプルソースはGitHubにも置いています。

サンプルソース(GitHub)

コンポジットパターンの注意点

コンポジットパターンでは、コンポジット(Composite)が任意の深さのツリーを作れるようにしておくことが重要となります。

Special Thanks

デザインパターン-Composite

RubyでCompositeパターン

11. Composite パターン

Amazon.co.jp: Rubyによるデザインパターン: Russ Olsen, ラス・オルセン, 小林 健一, 菅野 裕, 吉野 雅人, 山岸 夢人, 小島 努: 本

変更来歴

12/10 09:00 GitHubへのサンプルソースの設置。導入文の修正
12/11 00:00 書籍へのリンクをAmazon アフィリエイトに変更
12/15 10:35 ソースコードへの説明を追加
06/20 18:00 Ruby2.0.0対応、読みづらい部分を修正

おすすめの書籍