新人SEの学習記録

14年度入社SEの学習記録用に始めたブログです。もう新人じゃないかも…

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

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

第6章:Prototypeパターン

概要
  1. 種類が多すぎてクラスにまとめられない場合
    • 扱うオブジェクトの種類が多すぎ、1つ1つを別のクラスにするとソースファイルが大量になってしまう場合
  2. クラスからのインスタンス生成が難しい場合
    • 生成したいインスタンスが複雑な過程を経て作られるもので、クラスからつくり上げるのが難しい場合
    • 例えば、ユーザがマウスの操作によって作り上げた図形を表すインスタンス
    • 同じものを再び作りたい場合には、そのインスタンスを一旦保存しておき、それをコピーする
  3. フレームワークと生成するインスタンスを分けたい場合
サンプルプログラム
  • Productインタフェース
    • 複製を可能にするためのインタフェース
    • Clonableインタフェースを継承することで、cloneメソッドにより自動的に複製できるようになる
    • useメソッドは「使う」ためのもので、その意味はサブクラスの実装にまかされている
    • createCloneメソッドインスタンスの複製を行うためのもの
package chap6.framework;

public interface Product extends Cloneable{
	public abstract void use(String s);
	public abstract Product createClone();
}
  • Managerクラス
    • Productインタフェースを利用してインスタンスの複製を行うクラス
    • showcaseフィールドは、インスタンスの名前とインスタンスの対応関係を表現したもの
    • registerメソッドで、製品の名前とProductインタフェースを実装したクラスの組をshowcaseに登録
    • ソース中にMessageBoxというクラス名が出てこない=独立して修正が可能ということに注意
package chap6.framework;

public class Manager {
	private HashMap<String, Product> showcase = new HashMap<String, Product>();
	public void register(String name, Product proto) {
		showcase.put(name, proto);
	}
	public Product create(String protoname) {
		return showcase.get(protoname).createClone();
	}
}
  • MessageBoxクラス
    • Productインタフェースを実装
    • useメソッドは文字列を文字decocharで飾り枠のように囲んで出力する
    • createCloneメソッドは、自分自身の複製を行う。ここで呼んでいるcloneメソッドは自分自身を複製するメソッド
    • clone()を実行するクラスにCloneableインタフェースが実装されていないと、CloneNotSupportedExceptionが投げられる(詳細は後述)
package chap6;

public class MessageBox implements Product {
	private char decochar;
	public MessageBox(char decochar) {
		this.decochar = decochar;
	}
	@Override
	public void use(String s) {
		int length = s.getBytes().length;
		for (int i = 0; i < length + 4; i++) {
			System.out.print(decochar);
		}
		System.out.println();
		System.out.println(decochar + " " + s + " " + decochar);
		for (int i = 0; i < length + 4; i++) {
			System.out.print(decochar);
		}
		System.out.println();
	}

	@Override
	public Product createClone() {
		Product p = null;
		try {
			p = (Product)clone();
		} catch (CloneNotSupportedException e) {
			e.printStackTrace();
		}
		return p;
	}
}
package chap6;

public class Main {
	public static void main(String[] args) {
		Manager manager = new Manager();
		MessageBox mbox = new MessageBox('*');
		MessageBox sbox = new MessageBox('/');
		manager.register("warning box", mbox);
		manager.register("slash box", sbox);
		
		Product p1 = manager.create("warning box");
		p1.use("hogehoge");
		Product p2 = manager.create("slash box");
		p2.use("hogehoge");
	}

}
************
* hogehoge *
************
////////////
/ hogehoge /
////////////
登場人物
まとめと補足
  • クラスからインスタンス作っちゃダメなの?
    • 1) 種類が多すぎる場合:サンプルでは*と/で囲むインスタンスの2つだけだが、全ての場合についてクラスを作ると大変なことに
    • 2) クラスからの生成が難しい場合:本サンプルでは理解しにくい。冒頭のユーザが作った図形の例がわかりやすい
    • 3) フレームワークと分けたい場合:サンプルではcloneを行うのをframeworkパッケージに閉じ込めている、クラス名の束縛から分離
  • クラス名の束縛?
    • ソースプログラム中にクラス名が書かれていると何が問題なのか
    • そのクラスと切り離して、再利用することができなくなる
    • ソースを書き換えればクラス名の変更はできるが、ソースファイルが無い場合は?(classファイルのみ、など)
    • 密に結合しなければならないクラスの名前がソース中に書かれるのは当然、部品として独立させたいクラス名が書かれるのが問題
  • 関連するパターン
    • Flyweightパターン(20章):1つのインスタンス複数の場所で共有して利用
    • Mementoパターン(18章):スナップショットとアンドゥを行うために現在のインスタンスの状態を保存
    • Compositeパターン(11章)、Decoratorパターン(12章):複雑なインスタンスを動的に作る場合がある、Prototypeを使うと便利
    • Commandパターン(22章):登場する命令を複製したい場合、Prototypeを使う場合がある
  • 補足:cloneメソッドとCloneableインタフェース
    • cloneメソッドを実行する場合、対象となるクラスはCloneableインタフェースを実装している必要がある
    • cloneメソッドを呼び出すと、内部的には元のインスタンスと同じ大きさのメモリを確保し、フィールドの内容をコピーしている
    • Cloneableインタフェースを実装していないクラスがcloneメソッドを呼び出すと、CloneNotSupportedExceptionが発生
    • cloneメソッドはObjectクラスで定義、つまりどのクラスでもcloneメソッドを継承している
    • Cloneableインタフェースはメソッドを1つも定義していない、マーカーインタフェース
    • cloneはフィールドの内容をそのままコピーするだけ、フィールドが配列ならその参照がコピーされる(要素1つ1つはコピーされない)
    • このようなフィールド対フィールドのコピーをshallow copyと呼ぶ