【Java】Compositeパターン【デザインパターン】
目次
Compositeパターンについて
Compositeパターンとは
- 容器と中身を同一視し、再帰的な構造を作るパターンです
- Compositeは、混合物や複合物という意味です。
サンプルプログラム
- Compositeパターンを使って、ファイルとディレクトリを模式的に表現する例を取り上げます
- 各クラスの役割は以下のようになっています
名前 | 役割 |
---|---|
Entry(Component) | Fileと Directoryを同一視するクラス |
File(Leaf) | ファイルを表すクラス |
Directory(Composite) | ディレクトリを表すクラス |
FileTreatmentException | ファイルにEntryを追加しようとした時に起きる例外クラス |
Entryクラス
- 抽象クラスで、ディレクトリエントリを表現します
- サブクラスで、FileクラスとDiirectoryクラスが作られます
- printListは引数なしとありの2つのメソッドがあり、メソッドをオーバーロード(多重継承)しています
- printList()はpublic、printList(String)はprotectedとして、Entryクラスのサブクラスでのみ使われるようにしています
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); // prefixを前につけて一覧を表示する public String toString() { // 文字列表現 return getName() + " (" + getSize() + ")"; } }
Fileクラス
- Entryクラスで定義していた、抽象クラス(getName,getSize,printList(String))を全て実装します
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); } }
Directoryクラス
- getSizeメソッドの変数sizeにentryのサイズを加えていますが、このentryはFileのインスタンスでも、Directoryクラスのインスタンスでも、良いような使用になっています
- これが、容器と中身を同じものとみなす、ということになります
- entryはEntryのサブクラスのインスタンスなので、getSizeを呼ぶことができます
- Compositeパターンの再帰的構造が、getSizeメソッドの再帰的呼び出しに対応しています (printListも同様)
import java.util.Iterator; import java.util.ArrayList; 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); } } }
FileTreatmentExceptionクラス
- ファイルにaddメソッドを呼んでしまったときに投げられる例外です
public class FileTreatmentException extends RuntimeException { // RuntimeExceptionで正しいか? public FileTreatmentException() { } public FileTreatmentException(String msg) { super(msg); } }
Mainクラスで動作確認
- root,bin,tmp,usr ディレクトリを作り、binの下に vi,latex ファイルを作ります
- usrディレクトリのなかに、kiki,nausicaa,chihiro のディレクトリを作り、ファイルを格納します
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 tmpdir = new Directory("tmp"); Directory usrdir = new Directory("usr"); rootdir.add(bindir); rootdir.add(tmpdir); rootdir.add(usrdir); bindir.add(new File("vi", 10000)); bindir.add(new File("latex", 20000)); rootdir.printList(); System.out.println(""); System.out.println("Making user entries..."); Directory kiki = new Directory("kiki"); Directory nausicaa = new Directory("nausicaa"); Directory chihiro = new Directory("chihiro"); usrdir.add(kiki); usrdir.add(nausicaa); usrdir.add(chihiro); kiki.add(new File("diary.html", 100)); kiki.add(new File("Composite.java", 200)); nausicaa.add(new File("memo.tex", 300)); chihiro.add(new File("game.doc", 400)); chihiro.add(new File("junk.mail", 500)); rootdir.printList(); } catch (FileTreatmentException e) { e.printStackTrace(); } } }
Compositeパターンのメリット
- 全てのオブジェクト(File・Directory)は、共通の抽象クラス(Entry)を持っているので、クライアントから見て、中身(File・Directory)を意識する必要がなく、一様に扱うことができます
- 新たなクラスを追加した場合でも、基底クラス (Entry) のインターフェースが変わらなければ、 Clientの処理には影響しません
今日のポイント
- Compositeパターンとは、容器と中身を同一視し、再帰的な構造を作るパターンです
- つまり複数個のものを集めて、あたかも一つのものであるかのように取り扱います
- 全ての Leaf、Composite オブジェクトは、共通の抽象クラスを持っているので、 Client から見て、中身を意識する必要がなく、一様に扱うことができます
本日もお疲れ様です😊