LoginSignup

This article is a Private article. Only a writer and users who know the URL can access it.
Please change open range to public in publish setting if you want to share this article with other users.

More than 5 years have passed since last update.

Compositeパターンを触ってみる

Posted at

今日の目標

JavaのCompositeパターンとはどういったものなのかを知る

使うもの

ではスタート

はじめに

まずはCompositeパターンとはどのようなものなのかをWikipediaさんで調べてみる。

Composite パターン(コンポジット・パターン)とは、GoF(Gang of Four; 4人のギャングたち)によって定義された デザインパターンの1つである。「構造に関するパターン」に属する。Composite パターンを用いるとディレクトリとファイルなどのような、木構造を伴う再帰的なデータ構造を表すことができる。
Composite パターンにおいて登場するオブジェクトは、「枝」と「葉」であり、これらは共通のインターフェースを実装している。そのため、枝と葉を同様に扱えるというメリットがある。(wikipedia-Compositeパターン

サンプルにもありましたが、ディレクトリとファイルを思い浮かべるとわかりやすいですね。ディレクトリ(枝)にはファイル(葉)とディレクトリ(枝)を含むことができます。そゆことです。

本のサンプルを試してみる

ということで、サンプルを試してみます。サンプルはディレクトリとファイルの構造を模したものを作る、というものです。登場人物は下記のとおり。

  • Entry
    • FileとDirectoryの抽象クラス
  • File
    • Fileクラス。Entryを実装してます
  • Directory
    • Directoryクラス。Entryを実装してます
    • ディレクトリの中にディレクトリを含むことができます。表示やサイズ計算に再起的呼び出しが使われています

最初はEntryクラス。

Entry.java
package composite;

import exception.FileTreatmentException;

/**
 * ディレクトリエントリを表現するクラス
 * @author torinist
 *
 */
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("");
    }

    // prefixを前につけて一覧を表示する
    protected abstract void printList(String prefix);

    // 文字列表現
    public String toString() {
        return getName() + " (" + getSize() + ")";
    }
}

続いてFileクラス。Fileクラスは単純です。自分自身の名前とサイズを持って返すだけ。

File.java
package composite;

public class File extends Entry {

    private String name;
    private int size;

    // コンストラクタ
    public File(String name, int size) {
        this.name = name;
        this.size = size;
    }

    @Override
    public String getName() {
        return name;
    }

    @Override
    public int getSize() {
        return size;
    }

    @Override
    protected void printList(String prefix) {
        System.out.println(prefix + "/" + this);
    }

}

次はDirectoryクラス。Directoryクラスはその中にDirectoryクラスを持つことができます。DirectoryクラスのインスタンスにDirectoryクラスのインスタンスをaddすると、そのインスタンスのdirectoryフィールドに追加されます。printListやgetsizeをするときには、directoryフィールドの中身を見て、そこにオブジェクトがあったらオブジェクトに対してメソッドを呼び出すという処理をしてます。そうすることによって、ディレクトリの中のディレクトリを処理してるのですね。なるほど。

Directory.java
package composite;

import java.util.ArrayList;

public class Directory extends Entry {

    private String name;
    private ArrayList<Entry> directory = new ArrayList();

    public Directory(String name) {
        this.name = name;
    }

    @Override
    public String getName() {
        return name;
    }

    // directory内にあるファイルのサイズを足して返す
    // ディレクトリだった場合は再起処理
    @Override
    public int getSize() {
        int size = 0;
        for(Entry e : directory) {
            size += e.getSize();
        }
        return size;
    }

    public Entry add(Entry entry) {
        directory.add(entry);
        return this;
    }

    @Override
    protected void printList(String prefix) {
        System.out.println(prefix + "/" + this);
        for(Entry e : directory) {
            e.printList(prefix + "/" + name);
        }
    }

}

おまけてきなException。Fileクラスに何か追加しようとするとExceptionが発生します(ファイルにファイルやディレクトリを入れることはできないですからねえ)

FileTreatmentException.java
package exception;

/**
 * ファイルに対する処理例外
 * @author torinist
 *
 */
public class FileTreatmentException extends RuntimeException {
    public FileTreatmentException() {}

    public FileTreatmentException(String msg) {
        super(msg);
    }
}

最後に実行クラス。Mainクラスではいくつかのディレクトリとファイルを作って、入れ子構造にしてます。大事なのは、Directoryインスタンス(nist)がFileインスタンス(readme.txt)もDirectoryインスタンス(workspace)も同じように処理している、ということですね。どちらもaddメソッドで対応してます。

Main.java
import composite.Directory;
import composite.File;
import exception.FileTreatmentException;

public class Main {

    public static void main(String[] args) {
        try {
            System.out.println("Making root entries...");

            Directory rootdir = new Directory("root");
            Directory bindir = new Directory("bin");
            Directory usrdir = new Directory("usr");
            rootdir.add(bindir);
            rootdir.add(usrdir);
            bindir.add(new File("vi", 10000));
            bindir.add(new File("latex", 20000));
            rootdir.printList();

            System.out.println("\nMaking user entries...");
            Directory tori = new Directory("tori");
            Directory nist = new Directory("nist");
            usrdir.add(tori);
            usrdir.add(nist);
            tori.add(new File("index.html", 100));
            tori.add(new File("main.html", 3000));
            nist.add(new File("readme.txt", 100));
            Directory workspace = new Directory("workspace");
            workspace.add(new File("index.html", 200));
            nist.add(workspace);
            rootdir.printList();
        } catch(FileTreatmentException e) {
            e.printStackTrace();
        }
    }

}

結果。

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

Making user entries...
/root (33400)
/root/bin (30000)
/root/bin/vi (10000)
/root/bin/latex (20000)
/root/usr (3400)
/root/usr/tori (3100)
/root/usr/tori/index.html (100)
/root/usr/tori/main.html (3000)
/root/usr/nist (300)
/root/usr/nist/readme.txt (100)
/root/usr/nist/workspace (200)
/root/usr/nist/workspace/index.html (200)

作ったファイルとディレクトリが全部表示されました。

なかなかディレクトリとファイル以外に思いつかないのですが…。
Compositeは容器の中に容器を入れる(今回の場合はディレクトリのなかにディレクトリ)ので、必ず再起的呼び出しが出てきそうです。再起苦手な私としてはCompositeをマスターして再起克服したいですね!

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