再帰的なデータ構造
OSのファイルシステムは、ファイルはフォルダに収められ、そのフォルダはさらにほかのファイルとともに上位のフォルダに、そのフォルダはさらに上位のフォルダに……、という具合に入れ子構造になって構成されています。このように、ある要素がそれと同等の要素を含むデータ構造を「再帰的なデータ構造」と呼びます。
ファイルシステムの表現
このような再帰的なデータ構造をプログラム上で表現してみます(図1)。ファイルとフォルダをそれぞれFile、Folderというクラスで表します。ファイルとフォルダそれぞれにgetValueというメソッドを用意することにします。FileのgetValueは自身のファイル容量を返し、Folderについてはそのフォルダが持つすべてのファイル容量の合計を返すとします(リスト1〜リスト3)。
*** 一部省略されたコンテンツがあります。PC版でご覧ください。 ***
*** 一部省略されたコンテンツがあります。PC版でご覧ください。 ***
*** 一部省略されたコンテンツがあります。PC版でご覧ください。 ***
上記のリストを実行すると、以下のような結果が得られます。
*** 一部省略されたコンテンツがあります。PC版でご覧ください。 ***
ここで紹介したプログラムは、FolderのgetValueを呼び出すと、Folderの子要素についてgetValueが再帰的に呼び出されるという、なかなかうまい仕組みを持っているように見えます。しかし、データ構造の要素が「ファイルであるかフォルダであるかを区別する」必要が生じ、そのためにファイル格納用、フォルダ格納用それぞれにArrayListをわざわざ用意しています。
この、決してスマートとはいえないコードは、拡張しようとしたときに問題点が明らかになります。例えば、Folderを拡張した新しいFolder2というクラスを作成することを考えてみましょう。Folder2はFolderと同等に扱うことができないため、Folderが持つArrayListはFile用、Folder用、Folder2用と3つ必要になります。addメソッドも新しく加えなければなりません。つまり、既存のコードに修正を加える必要があるということです。Folder2も、当然これら3つのArrayListを持たなければなりません。
問題点の解消
FileとFolderは、ここではgetValueというメソッドを持つ点以外は全く異なるクラスとして扱っていたため、インスタンスの区別は必然でした。しかし、この手間を省くうまい方法があります。FileとFolderが共通して継承するスーパークラスEntryを抽象クラスとして作成するのです(リスト4〜リスト6)。
*** 一部省略されたコンテンツがあります。PC版でご覧ください。 ***
*** 一部省略されたコンテンツがあります。PC版でご覧ください。 ***
*** 一部省略されたコンテンツがあります。PC版でご覧ください。 ***
FileとFolderをEntryクラスのサブクラスと見なすことにより、どちらもEntryクラスのオブジェクトとして統一的に扱うことができるようになります。つまりインスタンスの区別が不要になるのです。上の例で見たようにFolder2というクラスを追加することになっても、Folder2もまたEntryのオブジェクトと見なせるため、既存のコードの修正は必要ありません。
GoFのCompositeパターン
配下に子要素を含まない単純要素(ここではFile)と何かしらの要素を配下に含む複合要素(ここではFolder)という異なるクラスを、抽象クラスによって同等のものと見なし、統一的に扱い、再帰的なデータ構造を表現するこの手法は、GoF(Gang of Four)のデザインパターンによりCompositeパターンとしてまとめられています。
パターンの構造をクラス図で表すと、図2のようになります。
Component:抽象クラス、Leaf:単純要素を表すクラス、Composite:複合要素を表すクラス、Client:Composite内のオブジェクトを操作するクラス。この4つのクラスがCompositeパターンの基本となります。ComponentクラスがLeafクラスとCompositeクラスの共通のインターフェイスを宣言することで、ClientはLeafとCompositeのオブジェクトを区別することなく扱うことができます。
Copyright © ITmedia, Inc. All Rights Reserved.