コンポジット(Composite) | Ruby デザインパターン


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

🐠 コンポジットとは?

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

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

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

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

🚕 コンポジットのメリット

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

👽 ソースコード

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

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

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

# FileEntry, DirEntryクラスの共通メソッドを規定(Component)
class Entry
# ファイル/ディレクトリの名称を返す
def get_name
end
# ファイル/ディレクトリのパスを返す
def ls_entry(prefix)
end
# ファイル/ディレクトリの削除を行う
def remove
end
end

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

# 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メソッドを持っています。

# 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

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

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メソッドで同一のものとみなして処理できていることがわかります。(再帰的にメソッドを呼び出せていることがわかります)

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

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

😼 サンプルソース

🗻 参考リンク

📚 おすすめの書籍

🖥 サーバについて

このブログでは「Cloud Garage」さんのDev Assist Program(開発者向けインスタンス無償提供制度)でお借りしたサーバで技術検証しています。 Dev Assist Programは、開発者や開発コミュニティ、スタートアップ企業の方が1GBメモリのインスタンス3台を1年間無料で借りれる心強い制度です!(有償でも1,480円/月と格安)