LoginSignup
5
6

More than 5 years have passed since last update.

Compositeパターン

Last updated at Posted at 2015-03-26

1. 概要

  • 容器と中身の同一視
  • ディレクトリとファイルを同一視する
  • 容器と中身を同一視し,再帰的な構造を作る

2. 目的

あるフォルダ以下のファイルやフォルダをすべて削除したい場合など、それがファイルなのかフォルダなのかを意識せずに、同じように削除できたほうが都合が良い。

3. 簡単な例(マインスイーパー)

(1)例

ここには爆弾ないだろうと思い、あるマスを選ぶ

爆弾はなかった。それで周辺の爆弾がないであろうマスを自動で解放する。

(2)説明

  1. マスを解放する。
  2. 隣り合うマスがセーフなマスかアウトなマスか情報を取得。
  3. アウトだったら自動解放処理を中止、セーフなら自動解放処理を続行。
  4. アウトなマスにあたるまで自動マス解放処理を継続します。

⇒セーフかアウトかわからないけど処理をして、セーフの場合は処理を続ける。セーフとアウトを同一視して処理を行い。セーフであれば続けるという再帰的な構造

~再帰的とは~
関数やメソッドなどの記述の中に、その関数など自体への呼び出しを行なうコードが含まれることを「再帰呼び出し」(recursive call:リカーシブコール)という。

4. 実例

(1)作成物

①Entry.java ⇒ ディレクトリとファイルを表す抽象クラス
②File.java ⇒ ファイルを表すクラス
③Directory.java ⇒ ディレクトリを表すクラス
④FileTreatmentException.java ⇒ ファイルにエントリを追加しようとしたときに起きる例外クラス
⑤Main.java ⇒ 動作用のクラス

(2)プログラム作成

①Entry.java

Entry.java
public abstract class Entry {
    public abstract String getName();
    public abstract int getSize();
    public Entry add(Entry entry)throws FileTreatmentException{
        throw new FileTreatmentException();
    }
    public void printList(){
        printList("");
    }
    protected abstract void printList(String prefix);

    public String toString(){
        return getName() + " (" + getSize() + ")";
    }
}
  • getName()
    ディレクトリエントリ(ディレクトリとファイルのことをまとめて呼ぶ)
    は名前を持っていて、名前を取得する抽象メソッド。

  • getSize()
    ディレクトリエントリのサイズ(ファイルのサイズ)を持っていて、このサイズを取得する抽象メソッド。

  • add()
    ディレクトリの中にディレクトリを入れるメソッド。

  • printList()
    一覧を表示するメソッド。
    引数ありと、引数なしバージョンのメソッドがあります(オーバーロード)。

  • toString()
    自分の名前とサイズを取得して文字列として返すメソッド。


②File.java

File.java
public class File extends Entry{
    private String name;
    private int size;
    public File(String name,int size){
        this.name = name;
        this.size = size;
    }
    public String getName(){
        return name;
    }
    public int getSize(){
        return size;
    }
    protected void printList(String prefix){
        System.out.println(prefix + "/" + this);
    }
}
  • File() コンストラクで名前とサイズを与えてFileのインスタンスを作成する 例) new File(.gitignore,500);

③Directory.java

Directory.java
public class Directory extends Entry {
    private String name;
    private ArrayList directory = new ArrayList();
    public Directory(String name){
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public int getSize() {
        int size = 0;
        Iterator it = directory.iterator();
        while(it.hasNext()){
            Entry entry = (Entry)it.next();
            size += entry.getSize();
        }
        return size;
    }
    public Entry add(Entry entry){
        directory.add(entry);
        return this;
    }
    protected void printList(String prefix) {
        System.out.println(prefix + "/" + this);//コンソール出力
        Iterator it = directory.iterator();
        while(it.hasNext()){
            Entry entry = (Entry)it.next();
            entry.printList(prefix + "/" + name);
        }
    }
}
  • ディレクトリを表現するクラス
  • nameフィールドはあるがsizeフィールドは動的に計算するためにない
  • directoryフィールドはディレクトリエントリを保持しておくためのフィールド
  • getSize() directoryの要素を1つ1つ取り出して、そのサイズを合計したものが戻り値となる。size += entry.getSize();ここではFileであろうがDirectoryであろうがgetSize()を実装しているのでgetSize()でサイズを得ることができる。Compositeパターンの再帰的構造が、そのままgetSizeメソッドの再帰的呼び出しに対応している
  • add() ディレクトリの中にファイルやディレクトリを追加するもの。 引数がEntry entryなのはDirectoryのインスタンスかFileのインスタンスかを調べることなくdirectoryフィールドに追加している。
  • printList() ディレクトリの一覧を表示するもの。 再帰的にprintListメソッドを呼び出している。

④FileTreatmentException.java

FileTreatmentException.java
public class FileTreatmentException extends RuntimeException {
    public FileTreatmentException(){
    }
    public FileTreatmentException(String msg){
        super(msg);
    }
}
  • ファイルに対してaddメソッドを呼び出してしまったときに投げられる例外。

⑤Main.java

Main.java
public class Main {
    public static void main(String[] args) {
        try{
            System.out.println("Make directory");
            Directory rootdir = new Directory("root");
            Directory vardir = new Directory("var");
            Directory tmpdir = new Directory("tmp");
            Directory userdir = new Directory("User");
            rootdir.add(vardir);
            rootdir.add(tmpdir);
            rootdir.add(usredir);
            vardir.add(new File("test",10000));
            vardir.add(new File("hoge",20000));
            rootdir.printList();

            System.out.println("");
            System.out.println("Make directory  again...");
            Directory nobunaga = new Directory("nobunaga");
            Directory hideyoshi = new Directory("hideyoshi");
            Directory ieyasu = new Directory("ieyasu");
            usrdir.add(nobunaga);
            usrdir.add(hideyoshi);
            usrdir.add(ieyasu);
            nobunaga.add(new File("diary.html",100));
            nobunaga.add(new File("Composite.java",200));
            hideyoshi.add(new File("memo.txt",300));
            ieyasu.add(new File("game.doc",400));
            ieyasu.add(new File("junk.mail",500));
            rootdir.printList();
        }catch(FileTreatmentException e){
            e.printStackTrace();
        }
    }
}
  • Directoryを4つ作成(root,var,tmp,usre)
  • rootディレクトリ内に他の3つのディレクトリを加える
  • vardirに2つのファイルを加える
  • 表示させる

↑のような作業を繰り返し

5. 結果

Making root entries...
/root (30000)
/root/bin (30000)
/root/bin/vi (10000)
/root/bin/latex (20000)
/root/tmp (0)
/root/usr (0)

Making user entries...
/root (31500)
/root/bin (30000)
/root/bin/vi (10000)
/root/bin/latex (20000)
/root/tmp (0)
/root/usr (1500)
/root/usr/nobunaga (300)
/root/usr/nobunaga/diary.html (100)
/root/usr/nobunaga/Composite.java (200)
/root/usr/hideyoshi (300)
/root/usr/hideyoshi/memo.txt (300)
/root/usr/ieyasu (900)
/root/usr/ieyasu/game.doc (400)
/root/usr/ieyasu/junk.mail (500)

6. まとめ

実例では、ファイルとディレクトリを同一視し、自身の中にある名前とサイズを取得する。ただし、ディレクトリの場合はその中に入っているファイルやディレクトリの名前とサイズを取得する。

容器と中身を同一視して、再帰的な構造を作成する

5
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
6