読者です 読者をやめる 読者になる 読者になる

新人SEの学習記録

14年度入社SEの学習記録用に始めたブログです。気づけば社会人3年目に突入。

学習記録:デザインパターン

[学習記録] デザインパターン

内容 第11章:Compositeパターン

概要
  • 容器と中身を同じ種類のものとして扱うと便利な場合がある
    • 例えばディレクトリとファイルはまとめて「ディレクトリエントリ」と呼ばれる
    • 容器の中には中身を入れてもいいし、もっと小さな容器を入れてもいい
    • 入れ子になった構造、再帰的な構造を作るためのパターンをCompositeパターンと呼ぶ。
サンプルプログラム
  • Javaのパッケージとソースファイルで出来た階層構造を表す。
Entry SourceFileとPackageを同一視する抽象クラス
SourceFile Javaのソース・ファイルを表す
Package Javaのパッケージを表す
SourceFileTreatmentExcepiton ソース・ファイルにEntryを追加しようとしたときに起こる例外。中身は空
Main 動作テスト用
  • Entry
    • 名前とサイズを得るメソッドを持つ
    • パッケージにEntry(=ソースファイルもしくはパッケージ)を追加するaddメソッド
    • 実装方法は色々あるが、addはここでは単に例外を投げる実装になっている。Packageクラスでオーバライドする
    • 一覧を表示するprintListメソッド
public abstract class Entry {
	public abstract String getName();
	public abstract int getSize();
	public Entry add(Entry entry) throws SourceFileTreatmentException {
		throw new SourceFileTreatmentException();
	}
	public void printList() {
		printList("");
	}
	public abstract void printList(String prefix);
	@Override
	public String toString() {
		return String.format("%s (%d)", getName(), getSize());
	}
}
  • SourceFile
    • 名前とサイズを持つ
    • printListでは、prefixと自身の文字列表現を"."で区切って表示する
    • thisを渡す=this.toString()を呼ぶのと同じ意味
public class SourceFile extends Entry {
	private String name;
	private int size;

	public SourceFile(String name, int size) {
		this.name = name;
		this.size = size;
	}
	@Override
	public String getName() {
		return name;
	}
	@Override
	public int getSize() {
		return size;
	}
	@Override
	public void printList(String prefix) {
		System.out.println(prefix + "." + this);
	}
}
  • Package
    • 名前と自身の持つEntryを保持するリストを持つ
    • サイズは持たずに、再帰的に計算する
    • addメソッドをオーバライドし、リストにEntryを追加している
import java.util.ArrayList;
import java.util.List;

public class Package extends Entry {
	private String name;
	private List<Entry> entryList = new ArrayList<Entry>();
	
	public Package(String name) {
		this.name = name;
	}
	@Override
	public String getName() {
		return name;
	}
	@Override
	public int getSize() {
		int size = 0;
		for (Entry entry : entryList) {
			size += entry.getSize();
		}
		return size;
	}
	@Override
	public Entry add(Entry entry) {
		entryList.add(entry);
		return this;
	}
	@Override
	public void printList(String prefix) {
		System.out.println(prefix + "." + this);
		for (Entry entry : entryList) {
			entry.printList(prefix + "." + name);
		}
	}
}
  • Main
    • jp.co.hogeパッケージ以下に、util、controller、model.business、model.daoパッケージを作成
    • 各パッケージにソース・ファイルを追加
public class Main {

	public static void main(String[] args) {
		Package jpPackage = new Package("jp");
		Package coPackage = new Package("co");
		Package hogePackage = new Package("hoge");
		Package utilPackage = new Package("util");
		Package controllerPackage = new Package("controller");
		Package modelPackage = new Package("model");
		Package daoPackage = new Package("dao");
		Package businessPackage = new Package("business");
		
		jpPackage.add(coPackage);
		coPackage.add(hogePackage);
		hogePackage.add(utilPackage);
		hogePackage.add(controllerPackage);
		hogePackage.add(modelPackage);
		modelPackage.add(daoPackage);
		modelPackage.add(businessPackage);
		
		utilPackage.add(new SourceFile("StringUtil.java", 125));
		utilPackage.add(new SourceFile("DBUtil.java", 500));
		controllerPackage.add(new SourceFile("HogeController.java", 2000));
		daoPackage.add(new SourceFile("HogeDao.java", 1000));
		businessPackage.add(new SourceFile("HogeBusiness.java", 1500));
		
		jpPackage.printList();
	}
}
.jp (5125)
.jp.co (5125)
.jp.co.hoge (5125)
.jp.co.hoge.util (625)
.jp.co.hoge.util.StringUtil.java (125)
.jp.co.hoge.util.DBUtil.java (500)
.jp.co.hoge.controller (2000)
.jp.co.hoge.controller.HogeController.java (2000)
.jp.co.hoge.model (2500)
.jp.co.hoge.model.dao (1000)
.jp.co.hoge.model.dao.HogeDao.java (1000)
.jp.co.hoge.model.business (1500)
.jp.co.hoge.model.business.HogeBusiness.java (1500)
まとめと補足
  • Compositeパターンの登場人物
    • Leaf役:中身を表す役。この中に他のものを入れることはできない。
    • Composite役:容器を表す役。LeafCompositeを入れることができる。
    • Component役:LeafCompositeを同一視するための役。共通のスーパクラスとして実現する。
    • Client役:Compositeパターンの利用者。
  • 容器と中身の同一視とは、複数と単数の同一視とも言える
    • 入力テストと出力テストをそれぞれまとめ、全てまとめて入出力テストに、といったこともCompositeパターンで実現可能
  • addメソッドの実装方法
    • Entryに実装し、例外を投げる(本実装):Packageでオーバライドする。SourceFileクラスでaddを呼ぶと例外になる。
    • Entryクラスに実装し、何も行わない:Packageでオーバライドする。SourceFileクラスでaddを呼んでも何も起きない。
    • Entryクラスで宣言するが、実装はしない:Package/SourceFileクラスで各々振る舞いを実装する。
    • Packageクラスにのみ実装する:Entry型の変数にaddする際は、Package型にキャストする必要あり。