Proxy, Abstract Factory, Builder

DesignPattern(No4)
2003/07/11
Proxy
特徴
Proxy 「代理人」を立てることで,(複数の処理から構成される)一つのまとまった処理を「代理人(Proxy)」
と「本人」に分割して処理を行う.(「代理人(Proxy)」と「本人」とも同一のメソッド名を持ち,このパターンを利
用するクラスから「代理人(Proxy)」クラスのみに命令を送ることで,決められた条件の下「代理人(Proxy)」ク
ラスから「本人」クラスへ処理を渡すことで処理の振り分けが行われる.(「代理人」から「本人」に処理が転送
される.))これにより,「本人」での作業負荷を低減させる.
Proxy パターンを利用する側は,「本人」の存在は知る必要がない.
クラス図
Subject インターフェースを継承(実装)す
ることで同一のメソッド名を持つことにな
り,Proxy クラスと RealSubject クラスが
同一視できる.(同様に扱える.)
Subject
request1( )
request2( )
request3( )
RealSubject の 「代理人」のクラス
インスタンスを持つ
(関連)
Client
Uses
「本人」のクラス
Proxy
Uses
RealSubject
RrealSubject
request1()
request2()
request3()
request1( )
request2( )
request3( )
各クラスの説明
Subject
<インターフェース>
Proxy
RealSubject
Client
Proxy クラスと RealSubject クラスを同一視するためのクラス(インターフェース(API:
Application Interface))
このクラスを継承(実装)する Proxy と RealSubject クラスは同一のメソッド名を有するこ
とになる.(同一視できる)
「代理人」の役となるクラス
Client からの要求を処理するクラスであり,RealSubject と(委譲により)関連付けること
で,Proxy の処理を RealSubject に廻すことを行う.
「本人」の役となるクラス
Proxy と同様に Subject クラスのインターフェース(API)を実装する.
Proxy より廻された処理(「Proxy で処理が行えななかった処理」とも見れる)を行う.
Proxy パターンを利用するクラス.
処理は Proxy クラス(のインスタンス)に対して行う.(RealSubject の存在は知る必要が
ない)
:Main
new
request1
:Proxy
request2
request3
new
request3
−1−
:RealSubject
DesignPattern(No4)
2003/07/11
プログラム例
PrinterProxy インスタンスを通して Printer インスタンスの処理を行なう.PrinterProxy インスタンスの print( )実
行時に Printer インスタンスを生成し,(印刷の)処理を委ねる.
クラスの説明
Printable
PrinterProxy
Printer
Main
PrinterProxy と Printer クラスを(メソッド名を)共通化するインタフェース
「代理人」の役割のクラス (印刷のメソッド print が呼び出されると処理を Printer に渡す.
もし,Printer インスタンスが存在しない場合は作成する.)
「本人」の役割のクラス (インスタンス生成時に5秒処理が停止する)
Proxy パターンを利用するクラス
<Printable.java> インターフェース(PrinterProxy と Priter を同一視する)
public interface Printable {
public abstract void setPrinterName(String name);
// 名前の設定
public abstract String getPrinterName();
// 名前の取得
public abstract void print(String string);
// 文字列表示(プリントアウト)
}
<PrinterProxy.java>
public class PrinterProxy implements Printable {
private String name;
// 名前
private Printer real;
// 「本人(Printer)」のインスタンス用変数
// コンストラクタ
public PrinterProxy( ) {
}
public PrinterProxy(String name) {
this.name = name;
}
// コンストラクタ
public synchronized void setPrinterName(String name) {
if (real != null) {
real.setPrinterName(name);
}
this.name = name;
}
public String getPrinterName( ) {
return name;
}
// 名前の取得
public void print(String string) {
realize( );
real.print(string);
}
private synchronized void realize( ) {
if (real == null) {
real = new Printer(name);
}
}
// 名前の設定
//「本人(Printer)」が存在すれば,
//
「本人(Printer)」にも設定する
// 自分のフィールドに引数の文字列を登録
Proxy パターン外より,このメソッド
が呼ばれる
// 表示 このメソッドが呼ばれ,以下の処理が行なわれる
//
①Printer のインスタンスが作成
//
②そのインスタンス(real)の print メソッドで表示処理
// 「本人(Printer)」のインスタンスを生成
synchronized にすることで Printer インスタン
スを同時に複数作成しない
Printer インスタンスは real に格納
}
<Printer.java>
public class Printer implements Printable {
private String name;
public Printer( ) {
−2−
DesignPattern(No4)
2003/07/11
コンストラクタを呼び出し(インスタ
ンスの作成)時に5秒間処理を停
止する
(heavyJob メソッドの実行)
heavyJob("Printer のインスタンスを生成中: * は 1 秒>>");
}
public Printer(String name) {
// コンストラクタ
this.name = name;
heavyJob("Printer のインスタンス(" + name + ")を生成中: * は 1 秒>>");
}
public void setPrinterName(String name) {
// 名前の設定
this.name = name;
}
public String getPrinterName() {
// 名前の取得
return name;
}
public void print(String string) {
// 名前つきで表示
System.out.println("=== " + name + " ===");
System.out.println(string);
}
private void heavyJob(String msg) {
// 重い作業(時間のかかる処理)
System.out.print(msg);
for(int i=0; i<5; i++){
try{
処理を停止させるプログラム
Thread.sleep(1000);
1秒毎に * を表示させる
}catch(Exception e){
}
System.out.print(" * ");
}
System.out.println("完了。");
}
}
<Main.java>
public class Main {
public static void main(String[] args) {
Printable p = new PrinterProxy("1st");
// PrinterProxy インスタンスの作成と”1st”の格納
System.out.println("名前は現在" + p.getPrinterName() + "です。");
// ”1st”の取得と表示
p.setPrinterName("2nd");
// PrinterProxy インスタンスに”2nd”を格納
System.out.println("名前は現在" + p.getPrinterName() + "です。");
// ”2nd”の取得と表示
p.print("Finish");
// PrinterProxy インスタンスの print()を実行
}
この処理により,“Printer インスタンスを作成”し,“Printer インス
}
タンスに”Finish””を渡して印刷”する.
<実行結果>
E:¥JAVA¥Sample>java Main
名前は現在 1st です。
名前は現在 2nd です。
この処理は“Printer インスタ
Printer のインスタンス(2nd)を生成中: * は 1 秒>> * * * * * 完了。
= = = 2nd = ==
ンス内で行われる.
Finish
−3−
DesignPattern(No4)
2003/07/11
Abstract Factory
特徴
インターフェースを利用して,抽象的な工場とその中で抽象的な部品を組み合わせて抽象的な製品を
作る枠組みを設定し,部品の具体的な実装には注目しないでインターフェース(API)のみに注目して製品
の組み立てる操作を行う.(具体的な操作を示さず,製品を作成するインタフェースを提供する.)
なお,具体的な製品の組み立ては抽象的な部品を構成するインターフェースを継承したクラス内で実現
する.
利用するケース
(1)クライアントが製品を生成する方法に依存しない場合
(2)アプリケーションが複数の製品の集合で構成される場合
(3)相互に互換性を保つためにオブジェクトをセットとして生成する場合
(4)クラスの集合を提供する場合にクラスの実装ではなく,インターフェース(API)を公開したい場合
クラス図
AbstractProduct1
AbstractProduct2
AbstractProduct3
executeA( )
executeB( )
doX( )
doY( )
performOne( )
performTwo( )
▲Creates
▲Creates
実装
AbstractFactory
各クラスのインスタンスを
作成するだけ
▲Creates
createProduct1( )
createProduct2( )
(インスタンスは保持しない)
ConcreteProduct1
ConcreteProduct2
ConcreteProduct3
executeA( )
executeB( )
doX( )
doY( )
performOne( )
performTwo( )
▲Creates
抽象的な工場と製品
これらがインタフェース(API)
となる
<これらのインスタンスは
作成しない>
▲Creates
実装
具体的な工場と製品
▲Creates
ConcreteProduct
createProduct1( )
createProduct2( )
各クラスの説明
AbstractProduct
(抽象クラス)
AbstractFactory
(抽象クラス)
ConcreteProduct
ConcreteFactory
[抽象的な製品]の役割
Abstractfactory「抽象的な工場」によって作り出される抽象的な部品や製
品のインターフェース(API)
「抽象的な工場」の役割
AbstractProduct「抽象的な製品」のインスタンスを作り出すためのインターフェー
ス(API)
こ の ク ラ ス 一 つ か ら 複 数 の 抽 象 的 な 製 品 「 Product 」 を 作 る た め , 複 数 の
AbstractProduct クラスと関連づいている
「具体的な製品」の役割
AbstractProduct「抽象的な製品」を実装するクラス(各 AbstractProduct のクラスを
継承する)
「具体的な工場」の役割
AbstractFactory「抽象的な工場」を実装するクラス
こ の ク ラ ス 一 つ か ら 複 数 の 具 体 的 な 製 品 「 Product 」 を 作 る た め , 複 数 の
ConcreteProduct クラスと関連ずいている
−4−
DesignPattern(No4)
2003/07/11
プログラム例
市ごとに異なる名簿(個人名,住所,電話番号)を登録するシステムを考える.
各市にはデフォルトに設定された内容(文字,記号,市外電話番号など)が存在するため,市ごとにシステム
を作ることが必要となる.しかしながら,基本的な処理内容はほぼ同じであり,汎用性を持たせるため,処理内
容を一般化させたインターフェース(API)を用意し,市ごとに異なるクラスをその API のクラスを継承して作成す
る.プログラム例は吹田市と堺市の個人データを格納するプログラムを構築した例である.
抽象的な工場と製品
これらがインタフェース(API)となる
Person
Address
name etc
postalCode etc
setName ( )
add ( )
getPostalCode ( )
setPostalCode ( )
PhoneNumber
phoneNumber
out( )
getCity( )
getPhoneNumber ( )
setPhoneNumber ( )
getCityCode( )
▲Creates
getPref ( )
AddressFactory
▲Creates
実装
▲Creates
createAddress( )
createPhoneNumber( )
Main
Suita_Address
Suita_Person
Suita_PhoneNumber
getCity ( )
getCityCode()
setPhoneNumber( )
setPhoneNumber( )
getPref ( )
getCityCode( )
▲Creates
▲Creates
実装
▲Creates
Suita_AddressFactory
createAddress( )
createPhoneNumber( )
具体的な工場と製品
( 全メソッドが定義されたクラス)
※ 各クラスのフィールド,メソッドの記述は一部省略
※ 上記クラス図では,具体的な工場と部品として Suita_AddressFactory (吹田市民の氏名,住所,電話番号を
生成するクラス)と生成されるクラス(Suita_Person など)の関係を示している.
異なる市民用に書き換えるには Suita_ を他の市(プログラム例では Sakai_)用に変更すれば良い.
※ Suita_Person のインタンスは Suita_Address, Suita_PhoneNumber のインスタンスを持つ(これで一人分)
クラス名
説明
抽象的な工場のクラス (具体的な実装クラスにインターフェースとなる)
AddressFactory
Person, Address, PhoneNumber を作成ための(メソッドを持つ)抽象クラス
Person
個人名を格納するための抽象クラス.さらに Address, PhoneNumber のインスタンスを
持つ.
Address
住所を格納するための抽象クラス
PhoneNumber
電話番号を格納するための抽象クラス
具体的な工場のクラス(吹田市民用の氏名,住所,電話番号を格納するデータベース用クラス)
Suita_AddressFactory
AddressFactory を継承し,Suita_Person,Suita_Address, Suita_ PhoneNumber のインス
タンスを作成するクラス
Suita_Person
吹田市民用の個人名を格納するためのクラス.デフォルトで“吹田市民”の文字列が存
在する.Person を継承し,Suita_Address と Suita_PhoneNumber と関連を持つ
Suita_Address
吹田市民用の住所を格納するためのクラス.デフォルトで“大阪府”,“吹田市”の文字
列が入っている.デフォルトで Address を継承.
Suita_PhoneNumber
吹田市民用の電話番号を格納するためのクラス.デフォルトで“06”の市外局番が入る
ようになっている.デフォルトで PhoneNumber を継承.
実行用クラス
Main
吹田市民一人(関大太郎)と堺市民一人(府大太郎)の情報を作成,格納し,表示する.
−5−
DesignPattern(No4)
2003/07/11
< AddressFactory .java>
package Factory;
// 抽象的表現のクラス(API のクラス)はパッケージ Factory に保存する
public abstract class AddressFactory {
// 引数で与えられたクラスのインスタンスを作成して戻すメソッド(Clas クラスの newInstance( )を利用)
public static AddressFactory getFactory(String classname){
AddressFactory factory=null;
try{
factory= (AddressFactory)Class.forName(classname).newInstance( );
}catch(ClassNotFoundException e){
System.out.println("クラス"+classname+" が見つかりません");
}catch(Exception e){
e.printStackTrace();
}
return factory;
// 作成したインスタンスを返す
}
public abstract Person createPerson();
public abstract Address createAddress();
public abstract PhoneNumber createPhoneNumber();
}
< Person .java>
package Factory;
// 抽象的表現のクラス(API のクラス)はパッケージ Factory に保存する
public abstract class Person {
protected String name;
protected Address adr;
protected PhoneNumber phnum;
public void setName(String name){
this.name=name;
}
public void add(Address adr){
this.adr= adr;
}
//---多重定義
public void add(PhoneNumber phnum){
this.phnum=phnum;
}
//--多重定義
public abstract void out();
}
< Address.java>
package Factory;
public abstract class Address {
protected String street;
protected String pref;
protected String city;
protected String region;
protected String postalCode;
protected static final String SPACE=" ";
public String getStreet(){return street;}
public String getPostalCode(){return postalCode;}
public String getRegion(){return region;}
public abstract String getCity();
public abstract String getPref();
public String getFullAddress(){
−6−
インターフェース(API)内のフィール
ド値を private とすると子クラスでも
パッケージが異なるのでアクセスで
きなくなる。
このため protected とする。
DesignPattern(No4)
2003/07/11
return postalCode+SPACE+pref+SPACE+city+SPACE+region+SPACE+street;
}
public void setStreet(String newStreet){street= newStreet;}
public void setRegion(String newRegion){region= newRegion;}
public void setPostalCode(String newPostalCode){postalCode=newPostalCode;}
}
< PhoneNumber .java>
package Factory;
public abstract class PhoneNumber {
protected String phoneNumber;
public String getPhoneNumber(){return phoneNumber;}
public abstract String getCityCode();
public abstract void setPhoneNumber(String newNumber);
}
< Suita_AddressFactory .java>
package SuitaFactory;
// 具体的表現のクラスは該当するファイルをまとめたパッケージ(** Factory)に保存する
import Factory.*;
// インターフェース用クラスを継承するため,Facory パッケージをインポートする
public class Suita_AddressFactory extends AddressFactory {
public Person createPerson(){
//---インスタンスを作るだけ(そして呼出し先へ戻す)
return new Suita_Person();
}
public Address createAddress(){
//---インスタンスを作るだけ(そして呼出し先へ戻す)
return new Suita_Address();
}
public PhoneNumber createPhoneNumber(){ //---インスタンスを作るだけ
return new Suita_PhoneNumber();
}
}
< Suita_Person.java >
package SuitaFactory;
import Factory.*;
吹田市用の特徴
public class Suita_Person extends Person{
public void out(){
System.out.println("吹田市民:"+name+"さんのデータです");
if(adr!= null){
System.out.println(adr.getFullAddress());
}else{
System.out.println("住所が設定されていません");
}
if(phnum!=null){
System.out.println(phnum.getPhoneNumber());
}else{
System.out.println("電話番号が設定されていません");
}
}
}
< Suita_Address .java>
package SuitaFactory;
import Factory.*;
public class Suita_Address extends Address {
−7−
DesignPattern(No4)
2003/07/11
private static final String PREF= "大阪府";
private static final String CITY= "吹田市";
Suita_Address(){
Super();
Pref=PREF;
City=CITY;
}
public String getCity(){return PREF;}
public String getPref(){return CITY;}
}
< Suita_PhoneNumber .java>
package SuitaFactory;
import Factory.*;
public class Suita_PhoneNumber extends PhoneNumber {
private static final String CITY_CODE= "06";
private static final int NUMBER_LENGTH= 8;
public String getCityCode(){return CITY_CODE;}
public void setPhoneNumber(String newNumber){
if(newNumber.length()==NUMBER_LENGTH){
phoneNumber=CITY_CODE+newNumber;
setPhoneNumber(phoneNumber);
}
}
}
<Main.java>
import Factory.*;
public class Main {
public static void main(String[] args) {
//AddressFactory factory = Suita_AddressFactory( ); //-- これは実行できないので下の方法で実行
//吹田用 AddressFatory のインスタンス作成
AddressFactory factory1 = AddressFactory.getFactory("SuitaFactory.Suita_AddressFactory");
Person prsn1= factory1.createPerson();
PhoneNumber phne1 = factory1.createPhoneNumber();
Address adr1= factory1.createAddress();
prsn1.add(phne1);
prsn1.add(adr1);
//Suita_Person インスタンスを作成
//Suita_PhoneNumber インスタンスを作成
//Suita_Address インスタンスを作成
//上の2つのインスタンスを Suita_Person
// インスタンスと関連付ける(保持する)
// 吹田市民の関大太郎さんの情報を格納
//---氏名の設定
prsn1.setName("関大太郎");
//---住所の設定
adr1.setStreet("3-3-35");
adr1.setRegion("山手町");
adr1.setPostalCode("564-8680");
//---電話番号の設定
phne1.setPhoneNumber("63681121");
//---出力
prsn1.out();
メソッドの操作や参照変数の取り扱いでは
Suita_Person や Suita_Address など吹田に
関するクラスを意識する必要が無い
インターフェース(API)部のメソッドのみ扱っ
ていればよい。
AddressFactory factory2 = AddressFactory.getFactory("SakaiFactory.Sakai_AddressFactory");
Person prsn2= factory2.createPerson();
PhoneNumber phne2 = factory2.createPhoneNumber();
Address adr2= factory2.createAddress();
prsn2.add(phne2);
prsn2.add(adr2);
−8−
DesignPattern(No4)
2003/07/11
// 堺市民の府大太郎さんの情報を格納
//---氏名の設定
prsn2.setName("府大太郎");
//---住所の設定
adr2.setStreet("1-1");
adr2.setRegion("学園町");
adr2.setPostalCode("599-8531");
//---電話番号の設定
phne2.setPhoneNumber("2521161");
//---出力
prsn2.out( );
}
}
<実行結果>
E:¥>javac *.java
コンパイルの方法参照
E:¥>javac SuitaFactory¥*.java
E:¥>javac SakaiFactory¥*.java
E:¥>java Main
吹田市民:関大太郎さんのデータです
564-8680 大阪府 吹田市 山手町 3-3-35
0663681121
堺市民:府大太郎さんのデータです
599-8531 大阪府 堺市 学園町 1-1
0722521161
ファイルの保存方法
注意
Main.java
①プログラムは以下の階層構造で格納する
Factory
AddressFactory.java
Address.java
Person.java
SuitaFactory
phonNumber.java
Suita_AddressFactory.java
Suita_Address.java
Suita_Person.java
Suita_phonNumber.java
SakaiFactory
(Suita_Factory に類似)
コンパイルの方法
Main.java のあるフォルダ上から
javac *.java で Main.java と インターフェース(API)のクラスはコンパイルできるが,具体的な情報を持つクラスが
コンパイルできない。
javac Suita_AddressFactory\*.java でコンパイルする.
−9−
DesignPattern(No4)
2003/07/11
Builder
特徴
別クラスのインスタンスを構築することを目的としたクラスを定義し、それにより複雑なオブジェクトの生成を
容易にする.例えば,1つのメインの製品を際セすることにより,その製品内で複数のクラスの生成が可能とな
るがメインのクラスは常に1つだけの場合.
複雑なインスタンス(あるいは複数のインスタンスから構成されるシステム)を構築する場合に,命令先を1箇
所とし,強制的に複数の段階を経て(複数のインスタンス作成を通して)システムを構築するパターン.
適用される状況
・複雑な内部構造を持つ
・相互に依存する属性を持つ(複数のインスタンスが相互に依存する(相互に関連を持つ)場合に特に有効)
クラス図
Client
Director
Uses
builder
construct
抽象クラス
Builder
builderPart1( )
builderPart2( )
builderPart3( )
getResult( )
実装
Director クラス内のメソッド(concrete)内で
Builder クラスのメソッドを利用して目的とす
る一連の処理を行なう
パターンを使うもの(Client クラス)からは詳細
な処理内容を見せない
Builder クラスは ConcreteBuilder クラスの
インターフェースの役割をする
ConcreteBuilder1
ConcreteBuilder2
builderPart1( )
builderPart2( )
builderPart3( )
getResult( )
builderPart1( )
builderPart2( )
builderPart3( )
getResult( )
各クラスの説明
Builder
ConcreteBuilder
Director
Client
インスタンスを作成するためのインターフェース(API)を定めるクラス.
インスタンスの各部分を作るためのメソッドと最終的にできた結果を得るためのメソ
ッドを用意
Builder クラス内のメソッドを実装(定義)するクラス
実際に作成されるインスタンスのためのクラスであり,メソッドが定義される.
Builder クラスを利用してインスタンスを作成するクラス
実装においては,Builder クラスで宣言されたメソッドのみ利用する.(実行は Builder クラスを継
承した ConcreteBuilder クラス内のメソッドを利用する.)
Builder パターンを利用するクラス
Director クラスのみ操作する.(Builder 関連のクラスは直接操作しない.)
−10−
DesignPattern(No4)
2003/07/11
:Client
:Director
:ConcreteBuilder
buildPart1
buildPart2
buildPart3
getResult
プログラム例
Builder パターンを使って,夏休み期間の「図書館の開館時間」の掲示を作成する.表示するテンプレートが
予め2種類存在しており,実行時の引数で設定できるようにする.
テンプレートは①標準的なもの(TextBuilderA クラス),②顔文字を利用したもの(TextBuilderB クラス)とし,実
行時引数はそれぞれ「normal」,「kao」とする.
Director クラス内の constract メソッド内に表示する内容が直接に記述されている.なお,このメソッド内で
Builder クラスの(部品化した)メソッドを利用している.
画面表示する文字列(改行も含む)を StringBuffer のインスタンス内に格納している点に注意.(最後に,この
中に入っている文字列を String 型にして画面表示する.)
< Builder .java>
インターフェース(API)となるクラス
public abstract class Builder {
public abstract void makeTitle(String title); //タイトルの表示(タイトル用バーナーを含む)
public abstract void makeString(String str);
//項目の表示(項目用バーナーを含む)
public abstract void makeItems(String[] items);//各項目に関する内容を箇条書きに表示(バーナーを含む)
public abstract Object getResult();
}
< TextBuilderA .java>
具体的な処理を行なうクラス(標準的な表示)
public class TextBuilderA extends Builder {
private StringBuffer buffer = new StringBuffer( );
// このフィールドに文書を構築
public void makeTitle(String title) {
// タイトル
buffer.append("======================================¥n");
// 飾り線
buffer.append("
<<<<" + title + ">>>¥n");
buffer.append("======================================¥n");
// 飾り線
buffer.append("¥n");
// 空行
}
public void makeString(String str) {
// 文字列
buffer.append('○' + str + "¥n");
// ○つきの文字列
buffer.append("¥n");
// 空行
}
public void makeItems(String[] items) {
// 箇条書き
for (int i = 0; i < items.length; i++) {
buffer.append("
・" + items[i] + "¥n");
// ・つきの項目
}
buffer.append("¥n");
// 空行
}
public Object getResult() {
// 完成した文書
return buffer.toString();
// StringBuffer を String に変換して戻す
}
}
< TextBuilderB .java>
具体的な処理を行なうクラス(顔文字を利用した表示)
public class TextBuilderB extends Builder {
−11−
DesignPattern(No4)
2003/07/11
private StringBuffer buffer = new StringBuffer();
// このフィールドに文書を構築
public void makeTitle(String title) {
// タイトル
buffer.append("******* (-_-;) ************** (-_-;) ****¥n");
// 飾り線
buffer.append("
<<<<" + title + ">>>¥n");
buffer.append("******* (-_-;) ************** (-_-;) ****¥n");
// 飾り線
buffer.append("¥n");
// 空行
}
public void makeString(String str) {
// 文字列
buffer.append("--- " + str + " ---¥n");
// ○つきの文字列
buffer.append("¥n");
// 空行
}
public void makeItems(String[] items) {
// 箇条書き
for (int i = 0; i < items.length; i++) {
buffer.append("
" + items[i] + "¥n");
}
buffer.append("¥n");
// 空行
}
public Object getResult() {
// 完成した文書
return buffer.toString();
// StringBuffer を String に変換して戻す
}
}
< Director .java>
Builder パターンを利用して一連の処理を管理するクラス
public class Director {
private Builder builder;
// Builder インスタンス(の参照)をフィールドに保持
public Director(Builder builder) {
// コンストラクタの引数として,Builder インスタンスを与え,
this.builder = builder;
//
フィールドに与える
}
// 文書構築 <重要> ある目的に対して Builder クラスのメソッドを利用して一連の処理を行なう
public Object construct() {
builder.makeTitle("図書館の開館時間");
// タイトル
builder.makeString("8 月 1 日∼8 月 31 日");
builder.makeItems(new String[ ]{
"9:00∼12:00",
"13:00∼17:00"
});
builder.makeString("9 月 1 日∼9 月 31 日");
builder.makeItems(new String[]{
"10:00∼12:00",
"13:00∼16:00"
});
return builder.getResult( );
// 文字列
// 箇条書き
// 別の文字列
// 別の箇条書き
// できた文書が戻り値になる(戻り値は Object の型で中身は String 型)
}
}
<Main.java> Bulder パターンを利用するクラス
public class Main {
public static void main(String[] args) {
if (args.length != 1) {
usage();
System.exit(0);
}
if (args[0].equals("normal")) {
Director director = new Director(new TextBuilderA( ));
String result = (String)director.construct();
−12−
//--標準的な表示
// ここで該当する具体的なクラスを
呼び出す
// 一連の操作を命令
DesignPattern(No4)
2003/07/11
System.out.println(result);
// 画面出力
} else if (args[0].equals("kao")) {
//--顔文字を利用した表示
Director director = new Director(new TextBuilderB());
String result = (String)director.construct();
System.out.println(result);
} else {
usage();
System.exit(0);
}
}
public static void usage() {
System.out.println("Usage: java Main normal
System.out.println("Usage: java Main kao
}
標準的な表示");
顔文字を利用した表示");
}
<実行結果>
E:¥ >java Main normal
======================================
<<<<図書館の開館時間>>>
======================================
○8 月 1 日∼8 月 31 日
・9:00∼12:00
・13:00∼17:00
○9 月 1 日∼9 月 31 日
・10:00∼12:00
・13:00∼16:00
E:¥>java Main kao
******* (-_-;) ************** (-_-;) ****
<<<<図書館の開館時間>>>
******* (-_-;) ************** (-_-;) ****
--- 8 月 1 日∼8 月 31 日 --9:00∼12:00
13:00∼17:00
--- 9 月 1 日∼9 月 31 日 --10:00∼12:00
13:00∼16:00
Main
Director
Uses
builder
construct
メソッド concrete 内で Builder クラスのメソッド
を利用して画面表示の構成を作成する
Builder
makeTitle ( )
makeString ( )
makeItems ( )
getResult ( )
抽象クラス
実装
(ConcreteBuilder のメソッドを組み合
わせて目的とする処理を作成する.)
Main クラスからは concrete メソッドの
み操作すれば良い
Builder クラスは ConcreteBuilder クラ
スのインターフェースの役割をする
−13−
TextBuliderA
TextBuliderB
makeTitle ( )
makeString ( )
makeItems ( )
getResult ( )
makeTitle ( )
makeString ( )
makeItems ( )
getResult ( )
DesignPattern(No4)
2003/07/11
:Client
:Director
:TextBuilderA
constract()
:TextBuilderB
複雑な一連の処理
makeTitle()
複雑な一連の処
理対して一つの命
令のみで実行
makeString( )
makeItems( )
makeString( )
makeItems( )
getResults( )
Director と TextBuilderA 間の処理に一致
Prototype
特徴
既存のインスタンスをコピーして新しいインスタンスを作成するパターン
(クラスから新しいインスタンスを作るのではなく,既存のインスタンスから別のインスタンスを作成する.
既存のインスタンスがその時点での状態を表していれば,その時点での状態を初期値にしたインスタンス
を作成することと見れる.)
Protetype パターンを利用する場合
(1)インスタンス化されるクラスが,実行時に明らかになる場合
(2)インスタンスが状態によって変化し,ある状態を初期値(とするインスタンス)として利用したい場合
Protetype パターンの利点
(1)生成するインスタンスの追加,削除を実行時に行なえる
(2)値を変えることでインスタンスの仕様を変更できる
(3)構造を変えることでインスタンスの仕様を変更できる
(4)サブクラス化を減らす
(5)アプリケーションのクラス構成を動的に決める
コピーの種類
浅いコピー クラスの最上位の要素(コピー対象のインスタンスのフィールド)だけが複製されること.
フィールド内に参照があってもそのまま(参照が)コピーされるため,参照先のインスタンスはコピ
ーしない.
このため,複製したインスタンスは(フィールドに参照を持った場合,)参照先のインスタンスを共
有することになるため,複製したインスタンスの一つが参照先のインスタンス変更すると他の複
製した全インスタンスに影響する.
深いコピー 最上位の属性(コピー対象のインスタンスのフィールド)だけではなく,下位のインスタンス(関連ず
いているインスタンス)も複製される.
任意の複合構造を持つオブジェクトでは(参照関係にあるインスタンスもコピーするため)非常に
効率が悪い(時間がかかる).
しかし,複製したインスタンスは複製元のインスタンスとは独立しているので,変更しても影響し
ない.
クラス図
−14−
DesignPattern(No4)
2003/07/11
Client
インターフェース
とくに,Cloneable インターフェ
ースの clone メソッドを利用す
る場合に必要となる
Protetype
Uses
createClone( )
自分自身(インスタンス)の複製を行なうクラス
Main
ConcreteProtetype
createClone( )
各クラスの説明
Protetype
ConcreteProtrtype
Client
(1)自分のクラスのインスタンスを作成し,
(2)自分のフィールドをそのインスタンスにコピーし,
(3)作成したインスタンスの参照を戻す
メッソドを持つ。
インスタンスをコピーして新しいインスタンスを作成するためのメソッド
を宣言するクラス
インスタンスをコピーして新しいインスタンスを作るメソッドを実装,定義するクラス
通常,自分のインスタンスを作成し,フィールドをコピーして(参照を Client に)戻
す.
Protetepe パターンを用いて,インスタンスをコピーするメソッドによって新しいイン
スタンスを作るクラス
プログラム例
<Product.java> ( Cloneable インターフェースを利用するためのインターフェース )
public interface Product extends Cloneable {
//Cloneable インターフェースを実装
( これで clone メソッドが利用でき
る)
public abstract void use(String s);
public abstract Product createClone( );
// インスタンスのコピーを行なうためのメソッド
}
<Manager.java>
import java.util.*;
// Hashtable 用にパッケージを import
public class Manager {
private Hashtable htable = new Hashtable( );
//--HashTable の設定
public void register(String name, Product proto) {
htable.put(name, proto);
//--Hashtable に proto のインスタンスを name で登録
}
public Product create(String protoname) {
Product p = (Product)htable.get(protoname);
Product q= p.createClone( );
return q;
//--Hashtable より protoname で検索し,
該当するインスタンスを取り出す
//-- 取り出したインスタンスのクローンを作成
//--コピーを(呼び出し先に)戻す
}
}
<MessageOut.java>
public class MessageOut implements Product {
String name;
int count;
//--呼ばれた回数
int orginalNumber;
//-- オリジナルのインスタンスを作成した時に設定される値(変更しない)
public MessageOut(String name) {
this.name = name;
orginalNumber=1;
count=0;
}
//-- コンストラクタ
−15−
DesignPattern(No4)
2003/07/11
public void use(String s ) {
//-- フィールドの値を表示するメソッド
System.out.println("----インスタンス "+ s +"の内容表示----");
System.out.println(" name ="+name);
System.out.println(" orginalNumber="+orginalNumber);
System.out.println(" 呼ばれた回数 ="+count);
}
//--- 複製の作成 (戻り値の型が 自分(のクラス) ) 戻り値はコピーしたインスタンス(の参照)
public Product createClone( ) {
count++;
//-- 呼ばれた回数の設定(オリジナルとなるインスタンスの値を変える)
Product p = null;
try {
p = (Product)clone( );
//-- Cloneable クラスの clone メソッド(フィールドもコピーする)
} catch (CloneNotSupportedException e) {
e.printStackTrace( );
}
return p;
//-- コピーしたインスタンスを戻す
}
}
<Main.java>
public class Main {
public static void main(String[] args) {
// 準備
Manager manager = new Manager( );
MessageOut mssg = new MessageOut("original");
manager.register("original", mssg);
mssg.use("original");
// オリジナルのインスタンスを作成
// HashTable にインスタンスを登録
//-- クローンを作成する前の"original"インスタンスの表示①
// 生成
Product p1 = manager.create("original");
p1.use("1st");
mssg.use("original");
//-- "original"のインスタンスのコピーを作成
//--1番目のコピーの表示 ②
//-- コピー元の表示(上と一致するはず) ③
Product p2 = manager.create("original");
p2.use("2nd");
mssg.use("original");
//-- "original"のインスタンスのコピーを作成
//--2番目のコピーの表示 ④
//-- コピー元の表示(上と一致するはず) ⑤
}
}
<結果出力>
E:¥JAVA¥Sample>java Main
----インスタンス original の内容表示---name =original
orginalNumber=1
呼ばれた回数 =0
----インスタンス 1st の内容表示---name =original
orginalNumber=1
呼ばれた回数 =1
----インスタンス original の内容表示---name =original
orginalNumber=1
呼ばれた回数 =1
----インスタンス 2nd の内容表示---name =original
① の表示
② の表示
③ の表示
④ の表示
−16−
DesignPattern(No4)
2003/07/11
orginalNumber=1
呼ばれた回数 =2
----インスタンス original の内容表示---- ⑤ の表示
name =original
orginalNumber=1
呼ばれた回数 =2
Manager
Product
Hashtable htable
register( )
create( )
use ( )
createClone ( )
インターフェース
Cloneable インターフェースの clone
メソッドを利用 するため
Cloneable インターフェースを継承し
ている
自分自身(インスタンス)の複製を行なうクラス
MassageOut
Main
createClone( )で,
Cloneable インターフェースの clone メソッドを利
用して,自分のコピーを作成,作成したインスタン
スの参照を戻す
String name
int count
int orginalNumber
use ( )
createClone ( )
−17−