この記事で紹介するパターン
コンポジット パターン
どんなデザインパターン?
やはりウィキペディアがわかりやすかったので参照
Composite パターン(コンポジット・パターン)とは、GoF (Gang of Four; 4人のギャングたち) によって定義された デザインパターンの1つである。「構造に関するパターン」に属する。Composite パターンを用いるとディレクトリとファイルなどのような、木構造を伴う再帰的なデータ構造を表すことができる。
Composite パターンにおいて登場するオブジェクトは、「枝」と「葉」であり、これらは共通のインターフェースを実装している。そのため、枝と葉を同様に扱えるというメリットがある。
とのこと。
再帰的な構造の実装をするためのパターンといえる。
再帰的な構造って例えば何がある?
身近なものでいうと、ウェキペディアの例にもあるがファイルシステムがある。
ルートディレクトリ(最上位のディレクトリ)は、複数のファイルと複数のディレクトリ(各ディレクトリはさらに複数のディレクトリとファイル)を持っている。
Composite パターンを使うと何が良いのか?
・再帰的な構造を容易に扱うことができるようになる
ポイントは共通のインターフェースを実装すること
Composite パターンにおいて登場するオブジェクトは、「枝」と「葉」であり、これらは共通のインターフェースを実装している。そのため、枝と葉を同様に扱えるというメリットがある。
ファイルシステムに例えてここを理解する。
まず枝と葉はディレクトリとファイルに例えられる。
そして、共通のインターフェースを実装するの部分は、deleteを実行すれば、それがファイル(葉)だろうが、ディレクトリ(枝)だろうが削除したい。
sizeを実行すると、ファイル(葉)だろうが、ディレクトリ(枝)だろうが、サイズを知りたい。
ディレクトリを削除すれば、その下のファイル、ディレクトリ(このディレクトリの下にファイルやディレクトリがあればそれも)を削除する必要があるし、
ディレクトリのサイズを知ろうとすれば、その下のファイル、ディレクトリ(このディレクトリの下にファイルやディレクトリがあればそれも)のサイズを取得してsumしないといけない。
ファイルにもディレクトリにも共通のインターフェースを実装していると、上のような再帰的な処理を行うことができる。
登場人物
コードで例の前に登場人物を整理しておく。
今までの話を踏まえて整理すると
・ファイル
・ディレクトリ
がある。
もう一度説明すると、ディレクトリには複数のファイル、はたまた別のディレクトリが入るような関係。
ディレクトリはファイルの他に、ディレクトリそのものも格納することができる。
さらに、プログラム的に見ると、ファイル(中身)とディレクトリ(容器)は共通のインターフェースを実装するので、
・共通インターフェース(もしくは基底クラス)
があると良いか。
Rubyのコードで簡単な例を紹介してみる
さきほどあげたとおり3つの登場人物がいる。
・ファイル
・ディレクトリ
・共通インターフェース(もしくは基底クラス)
今回できるだけ少ないコードで紹介したいので
共通インターフェース(もしくは基底クラス)は省略する。(なんのために出した?)
# MyFileクラス
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class MyFile def initialize(name, size) @name = name @size = size end def name @name end def size @size end end |
ファイル名とファイルサイズを持つだけのシンプルなクラス。
# Directoryクラス
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 |
class Directory def initialize(name) @name = name @files = [] end def name @name end def files @files end def add_file(file) files << file end def size sum = 0 @files.each do |file| sum = sum + file.size end sum end end |
add_fileするとこのディレクトリの中に、ファイルを追加できる(もちろんディレクトも追加できる)
そして、sizeを実行するとこのディレクトリが持つファイルのサイズの合計を取得できる。
コンソールでsizeメソッドを試してみる
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 34 35 |
3つのファイルを作成 [1] pry(main)> my_file1 = MyFile.new('my_file1', 10) [2] pry(main)> my_file2 = MyFile.new('my_file2', 20) [5] pry(main)> my_file3 = MyFile.new('my_file3', 30) [6] pry(main)> [9] pry(main)> my_file1.size => 10 [10] pry(main)> my_file2.size => 20 [11] pry(main)> my_file3.size => 30 dir1という名前のディレクトリを作成し、file1とfile2を入れる [14] pry(main)> dir1 = Directory.new('dir1') [17] pry(main)> dir1.add_file(my_file1) サイズを取得 [19] pry(main)> dir1.size => 10 ファイルを追加してサイズを取得してみる [20] pry(main)> dir1.add_file(my_file2) [21] pry(main)> dir1.size => 30 dir2というディレクトリを作成し、file3を入れる 24] pry(main)> dir2 = Directory.new('dir2') [25] pry(main)> dir2.add_file(my_file3) [27] pry(main)> dir2.size => 30 [28] pry(main)> ディレクトリ1にディレクトリ2を入れる [29] pry(main)> dir1.add_file(dir2) [30] pry(main)> dir1.size => 60 |
ファイルとディレクトリは共通のインターフェースを実装しているので、ディレクトリも入れることができるのがポイント。
まとめ
・Composite パターンを使うと再帰的な構造を実装するのに役に立つ。
・sizeメソッドを共通のインターフェースとして実装しているのがポイント。(理解間違っていたら教えてください。)
・あれ、このコードでイテレータパターンを説明できる?