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−
© Copyright 2024 Paperzz