目次へ

11. Composite パターン

  • 2012/04/26 一部修正しました

11.1 Composite パターンとは

第11章では、Composite パターンを学びます。Composite とは、英語で「複合物」を意味する言葉です。 Composite パターンは、「容器と中身を同一視する」ことで、再帰的な構造の取り扱いを容易にするものです。

「容器と中身を同一視する」必要が生じる例として、ファイルシステムなどが挙げられます。 あるフォルダ以下のファイルやフォルダをすべて削除したい場合など、それがファイルなのかフォルダなのかを意識せずに、同じように削除できたほうが都合が良いでしょう。

11.2 サンプルケース

サンプルケースでは、ディレクトリとファイルを考えます。まずは、Composite パターンを意識せずに、ファイルとディレクトリを表すクラスを作成してみましょう。

public class File{
	private String name = null;
	public File(String name){
		this.name = name;
	}
	public void remove(){
		System.out.println(name + "を削除しました");
	}
}

ファイルを表す File クラスは、インスタンス変数 name を持ちます。また、remove メソッドが呼ばれると、「〜を削除しました」とだけ表示するものとします。 ディレクトリを表す、Directory クラスは、List オブジェクトとして、配下のディレクトリとファイルのオブジェクトを管理し、remove メソッドが呼ばれた場合には、list に保持しているオブジェクトをすべて削除してから、自らを削除するものとします(ここでは、「〜を削除しました」と表示するだけ)。

import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;


public class Directory{
    private List<Object> list = null;
    private String name = null;
    public Directory(String name){
        this.name = name;
        list = new ArrayList<Object>();
    }
    public void add(File file){
        list.add(file);
    }
    public void add(Directory dir){
        list.add(dir);
    }
    public void remove(){
        Iterator<Object> itr = list.iterator();
        while(itr.hasNext()){
            Object obj = itr.next();
            if(obj instanceof File){
                ((File)obj).remove();
            }else if(obj instanceof Directory){
                ((Directory)obj).remove();
            }else{
                System.out.println("削除できません");
            }
        }
        System.out.println(name + "を削除しました。");
    }
}

ディレクトリの remove メソッドが呼ばれると、list に保持しているオブジェクトが File クラスのインスタンスであるのか、Directory クラスのインスタンスであるのかをチェックして、各クラスにキャストしてからそれぞれの remove メソッドを呼び出すようにしています。

remove メソッド呼び出しの図

上記の2つのファイルを使って、以下のようなファイル構造を記述して、削除してみましょう。

ファイル構造の図
public class Test {
    public static void main(String args[]){
        File file1 = new File("file1");
        File file2 = new File("file2");
        File file3 = new File("file3");
        File file4 = new File("file4");
        Directory dir1 = new Directory("dir1");
        dir1.add(file1);
        Directory dir2 = new Directory("dir2");
        dir2.add(file2);
        dir2.add(file3);
        dir1.add(dir2);
        dir1.add(file4);
        
        dir1.remove();
    }
} 

実行結果は、以下のようになります。

file1を削除しました
file2を削除しました
file3を削除しました
dir2を削除しました
file4を削除しました
dir1を削除しました

問題なく動作しているようですね。めでたしめでたしです。「Composite パターンなんて必要ないじゃないですか」と思ってしまいます。ところがここで、「ディレクトリには、ディレクトリとファイルだけでなくシンボリックリンクも入るようにしたい」という要求が出てきました。
Directory クラスは、add(SymbolicLink link) なるメソッドを追加したり、remove メソッドの中で、SymbolicLink に対応できるようにしなければなりません。あなたはここで後悔するわけです「なぜ Composite パターンを利用しておかなかったのか」と。では、Composite パターンを利用して上記のファイル構造を記述していればどうなっていたでしょう。Composite パターンでは、容器の中身と入れ物を同一視します。同一視するために、容器と中身が共通のインタフェースを実装するようにします。それでは、File と Directory が共通のインタフェース DirectoryEntry を実装するようにしましょう。

public interface DirectoryEntry{
	public void remove();
}

DirectoryEntry インタフェースでは、remove メソッドのみを定義しています。これを実装する形で、File クラス、Directory クラスを実装すると、以下のようになります。

public class File implements DirectoryEntry{
    private String name = null; 
    public File(String name){
        this.name = name;
    }
    public void remove(){
        System.out.println(name + "を削除しました");
    }
}
import java.util.List; 
import java.util.ArrayList; 
import java.util.Iterator; 
  
  
public class Directory implements DirectoryEntry{ 
    private List<DirectoryEntry> list = null; 
    private String name = null; 
    public Directory(String name){ 
        this.name = name; 
        list = new ArrayList<DirectoryEntry>(); 
    } 
    public void add(DirectoryEntry entry){ 
        list.add(entry); 
    } 
    public void remove(){ 
        Iterator<DirectoryEntry> itr = list.iterator(); 
        while(itr.hasNext()){ 
            DirectoryEntry entry = itr.next(); 
            entry.remove(); 
        } 
        System.out.println(name + "を削除しました。"); 
    } 
}

このように、Directory クラス、File クラスを共に DirectoryEntry クラスを実装するクラスとすることで、 Directory クラスの remove メソッド内では、実態が File クラスのインスタンスであるのか、Directory クラスのインスタンスであるのかを気にせず、どちらも DirectoryEntry オブジェクトとして扱うことができるようになっています。まさに、容器と中身を同一視している状態です。

どちらも DirectoryEntry オブジェクトとして扱う

さて、このように Composite パターンを利用していることで、SymbolicLink クラスを追加する必要が生じた場合も、 柔軟に対応できます。Directory クラスのソースコードを修正する必要もありません。ただ、DirectoryEntry インタフェースを 実装するように、SymbolicLink クラスを実装すればよいのです。

public class SymbolicLink implements DirectoryEntry{
    private String name = null;
	public SymbolicLink(String name) {
		this.name = name;
	}
    public void remove(){
        System.out.println(name + "を削除しました");
    }
}

Composite パターンを利用することで、再帰的な構造の記述が容易になり、メンテナンス性も向上していることがわかるでしょう。

11.3 Composite パターンまとめ

Composite パターンの一般的なクラス図は以下のようになります。

Composite パターンの一般的なクラス図
[引用] 『Java言語で学ぶ デザインパターン入門』(結城浩 ソフトバンクパブリッシング株式会社出版 2001年)

↑このページの先頭へ

こちらもチェック!

PR
  • XMLDB.jp