Chain of Responsibility
特徴
複数のインスタンスを鎖(チェーン)状に繋げておき
,そのインスタンスの鎖を順次渡り歩いて,目的のイ
ンスタンスを決定する方法.ある要求が発生し,その要求を処理するインスタンスを直接的に決められな
い場合,順次該当するインスタンスを調べていき決定する仕組み.(鎖状の関連は線形リストの関連付け
に類似する)
このパターンでは「要求する側」と「処理する側」の結びつきを弱め,処理する個々のインスタンスをそれ
ぞれ部品として独立させることができる.
クラス図
Client Handler next request( ) ConcreteHandler1 request( ) Request ConcreteHandler2 request( ) ConcreteHandler3 request( ) 異なる処理を持つ部品となるクラス (順次調べられるインスタンス) 部品となる処理を順次 調べていくクラス各クラスの説明
Handler 要求を処理するインターフェース(API)の役割をするクラス 部品となる複数のインスタンスを順次調べていき,該当する処理を決定するクラス ConcreteHandler1,2… 要求を処理するための具体的な処理が定義されているクラス(具体的に処理を行な う役割りをするクラス) (通常)複数のクラスが存在し,異なる処理を行なう.このクラスは Handler クラスの抽 象メソッドを実装するため,同一名のメソッドから同一視される. Client 最初の ConcreteHandler に要求を出す役割をするクラス. :Client :ConcreteHandler1 request( ):ConcreteHandler2 :ConcreteHandler3 :ConcreteHandler4
request( ) request( ) request( ) :ConcreteHandler1 next :ConcreteHandler1 next :ConcreteHandler1 next :ConcreteHandler1 next インスタンス間での関連 インスタンスの探索方向
プログラム例 問題を解決するためのクラスを4種類用意する.(うち一つはないも解決しない.)これらのクラスは「ある整数を 与えられた時に,予め設定されている整数値(定数)に従って,問題を解決するかしないかを判断」する.4種類 のクラスより,インスタンス作成時に異なる定数を設定することで異なる条件で問題解決する処理を設定できる. プログラムでは6つの問題解決のインスタンスを作成する. 下図のシーケンス図 : Client alice : NoSupport request( ) bob : LimiteSupport charlie : SpecialSuppot diana : LimiteSupport request( ) request( ) request( ) : NoSupport next : LimiteSupport next : SpecialSuppot next : LimiteSupport next インスタンス間の 関連 インスタンスの探索方向 elmo : OddSupport fred : LimiteSupport : OddSupport next : LimiteSupport next サポートしない <100 のときサポート 429 のときサポート <200 のときサポート 奇数 のときサポート <300 のときサポート request( ) request( ) <Support.java> 問題解決用クラスの共通部分を供給するクラス
public abstract class Support {
private String name; // このトラブル解決者の名前
private Support next; // たらい回しの先 (鎖状関連のリンク先のインスタンス) public Support(String name) { // トラブル解決者の生成
this.name = name; }
public Support setNext(Support next) { // たらい回しの先を設定 (リンクの作成)
this.next = next; // 隣のインスタンスに関連付ける(リスト構造の作成) return next; // 戻り値はリンク先のインスタンス
}
public final void support(Trouble trouble) { // トラブル解決の手順
if (resolve(trouble)) { // もし,問題解決できるならば done(trouble); //
} else if (next != null) { next.support(trouble); } else {
fail(trouble); }
}
public String toString() { // 文字列表現 return "[" + name + "]";
}
protected abstract boolean resolve(Trouble trouble); // 解決用メソッド protected void done(Trouble trouble) { // 解決
System.out.println(trouble + " is resolved by " + this + "."); }
protected void fail(Trouble trouble) { // 未解決
System.out.println(trouble + " cannot be resolved."); }
}
public class NoSupport extends Support { public NoSupport(String name) { super(name);
}
protected boolean resolve(Trouble trouble) { // 解決用メソッド
return false; // 自分は何も処理せず,false を返す }
}
< LimitSupport .java> コンストラクタの引数で設定される値(limit)未満の数ならば処理(問題解決)するクラス
public class LimitSupport extends Support {
private int limit; // この番号未満なら解決できる
public LimitSupport(String name, int limit) { // コンストラクタ (引数は「名前」と「定数値(limit)」) super(name);
this.limit = limit; }
protected boolean resolve(Trouble trouble) { // 解決用メソッド
if (trouble.getNumber( ) < limit) { // trouble の値が limit 未満ならば問題解決する return true; // 問題解決したとして true を返す
} else {
return false; // 問題解決しないので false を返す
} } }
< SpecialSupport .java> 予め設定された定数の場合のみ問題解決するクラス
public class SpecialSupport extends Support {
private int number; // 問題解決するための定数値
public SpecialSupport(String name, int number) { // コンストラクタ(引数は「名前」と「定数値(limit)」) super(name);
this.number = number; }
protected boolean resolve(Trouble trouble) { // 問題解決用メソッド
if (trouble.getNumber( ) == number) { // trouble の値が number と一致する場合のみ解決する return true; // 問題解決したとして true を返す
} else {
return false; // 問題が解決できなかったとして false を返す }
} }
< OddSupport .java> 奇数の場合のみ問題解決するクラス
public class OddSupport extends Support {
public OddSupport(String name) { // コンストラクタ(引数は「名前」のみ) super(name);
}
protected boolean resolve(Trouble trouble) { // 問題解決用メソッド
if (trouble.getNumber() % 2 == 1) { // trouble の値が奇数の場合のみ解決する return true; } else { return false; } } } < Trouble .java > トラブルの情報を格納するクラス
public class Trouble {
private int number; // トラブル番号 public Trouble(int number) { this.number = number; } // トラブルの生成 public int getNumber( ) { return number; } // トラブル番号を得る public String toString( ) { return "[Trouble " + number + "]"; } // トラブルの文字列表現 }
<Main.java > パターンを利用するクラス public class Main {
public static void main(String[ ] args) {
Support alice = new NoSupport("Alice"); Support bob = new LimitSupport("Bob", 100);
Support charlie = new SpecialSupport("Charlie", 429); Support diana = new LimitSupport("Diana", 200); Support elmo = new OddSupport("Elmo"); Support fred = new LimitSupport("Fred", 300); // 連鎖の形成 alice.setNext(bob).setNext(charlie).setNext(diana).setNext(elmo).setNext(fred); // さまざまなトラブル発生 for (int i = 0; i < 500; i += 33) { alice.support(new Trouble(i)); } } } <実行結果> E:¥>java Main
[Trouble 0] is resolved by [Bob]. [Trouble 33] is resolved by [Bob]. [Trouble 66] is resolved by [Bob]. [Trouble 99] is resolved by [Bob]. [Trouble 132] is resolved by [Diana]. [Trouble 165] is resolved by [Diana]. [Trouble 198] is resolved by [Diana]. [Trouble 231] is resolved by [Elmo]. [Trouble 264] is resolved by [Fred]. [Trouble 297] is resolved by [Elmo]. [Trouble 330] cannot be resolved. [Trouble 363] is resolved by [Elmo]. [Trouble 396] cannot be resolved. [Trouble 429] is resolved by [Charlie]. [Trouble 462] cannot be resolved. [Trouble 495] is resolved by [Elmo].
問題解決のインスタンスを作成 する (変数の型が API の Support, 各インスタンスに名前を設定) 問題解決のインスタンスを鎖状 に関連付ける(リスト構造とする) 0∼499 までの 33 の倍数を「問 題」の番号として解決を求める Bob によって解決 Diana によって解決 429 なので Charlie によって解決 330 は解決できるインスタンスがない. シーケンス図の点線矢印を伝って false が渡されていく
Command
特徴
命令(ユーザーの操作)をインスタンスとして表現する方法. 何らかの処理,操作を行なうための機能とデータの両方をカプセル化する.これにより,操作を実行するタイミ ングと方法が切り離される.クラス図
Client RefinedAbstraction receiver execute( ) Command execute( ) Receiver action( ) Invoker :Invoker :Client execute( ) :Receiver action :ConcreteCommand new各クラスの説明
Command 命令のインターフェースを定義する役割のクラス.ConcreteCommand Command クラスの API およびメソッドを実装しているクラス
Receiver (受信者) Command クラス(実際は ConcreteCommand クラス)が実行するときに対象となるクラス 命令を受け取る先(クラス) Client (依頼者) ConcreteCommand のインスタンスを作成し,そのときに Receiver クラスに処理を割り当てる クラス(具体的には ConcreteCommand のインスタンスを Receiver クラスのインスタンスに渡 す). Invoker (起動者) 命令の実行を開始するクラス Command インターフェース(クラス)の execute メソッドを呼び出す
プログラム例
2 種類のバーナーを用意し,整数と文字列をバーナー付きで表示する.バーナーは整数が偶数か奇数かにより切り替える.(これらの2種類のバーナーによる表示は BannerCommand1,2 クラスに用意す る.)メインメソッドより任意の「正の整数」と「4文字の文字列」を生成し,該当するバーナーにより 画面表示する.この処理を5回行なう.そして,5回の処理(コマンド)が終了後,(コマンドをインスタ ンスとして(リスト内に)保存している Receiver インスタンスから)過去に行なった処理(コマンド)を再度 実行する. なお,本プログラムのファイル構成は以下である. < Receiver.java > package banner; import command.*; import java.util.*; public class Receiver {
// 履歴 (実行する(した)コマンドをリストに持つ)
// <具体的には MacroCommand インスタンス内にコマンドのリストが存在> private MacroCommand history;
// コンストラクタ (MacroCommand のインスタンス(リスト)を共有) public Receiver (MacroCommand history) {
this.history = history; }
// 履歴全体を再実行 public void exeReceiver( ) {
history.execute( ); // MacroCommand のメソッドを実行 }
}
< MacroCommand .java > package command;
import java.util.Stack; // undo メソッドを楽に実装するため(push, pop メソッドを利用するため)に Stack を利用
import java.util.Iterator;
// コマンドの集合を格納するクラス(集合は commands に格納される)
public class MacroCommand implements Command { // Command クラスを実装
// 命令の集合
private Stack commands = new Stack( );
// 実行 (commands 内のコマンドを順次実行する)
public void execute( ) {
Iterator it = commands.iterator( ); while (it.hasNext( )) { ((Command)it.next( )).execute( ); } } // 追加 (コマンドの追加)
public void append (Command cmd) { if (cmd != this) { Main.java (Command パターンを利用するクラス) < command > Command.java (各コマンドの実行用メソッドが宣言されたインターフェース ) MacroCommand.java (コマンド群をリストに登録,(まとめて)実行するためのクラス) < banner > BannerCommand1.java (偶数用バーナーによって表示するクラス) BannerCommand2.java (奇数用バーナーによって表示するクラス) Receiver.java (MacroCommand インスタンスを持ち,このインスタンス を管理,実行をするメソッドを持つク ラス)
commands.push (cmd); }
}
// 最後の命令を削除 public void undo( ) {
if (!commands.empty( )) { commands.pop( ); }
}
// 全部削除
public void clear( ) { commands.clear( ); }
}
< Command.java > package command;
public interface Command {
public abstract void execute( ); // 各コマンドの実行用メソッドの宣言 }
< BannerCommand1.java> package banner;
import command.Command;
public class BannerCommand1 implements Command { int number=0;
String str=""; // コンストラクタ
public BannerCommand1(int number, String str){ this.number=number;
this.str=str; }
// 実行メソッド public void execute(){
System.out.println("[偶数用バーナー]*****<Banner1>***> "+ number +":"+ str +"<**********"); } } < BannerCommand2 .java> package banner; import command.Command;
public class BannerCommand2 implements Command { int number=0;
String str=""; // コンストラクタ
public BannerCommand2(int number, String str){ this.number=number;
this.str=str; }
// 実行メソッド public void execute ( ){
System.out.println("[奇数用バーナー]---<Banner2>---"+ number +":"+ str +"---"); } } < Main .java> import command.*; import banner.*;
public class Main {
private static MacroCommand history = new MacroCommand( ); ///-- コマンドの集合を格納する
///-- 実行する場(インスタンス)を作成し,history を(Main と)共有 private static Receiver rc = new Receiver(history); // コンストラクタ
public static void main(String[] args) {
//--- コマンドインスタンスの作成とリストに登録
System.out.println("---¥n コマンドインスタンスの作成とリストに登録"); for(int i=0; i<5; i++){ //-- 登録を5回繰り返す int number=(int)(Math.random()*100); //--- 任意の整数を作成 String name=generateString( ); //--- 任意の文字列の作成 //--コマンドインスタンスの作成とリストに登録の中心となるメソッド を実行 SetCommand(number, name); } //--- 登録したコマンドをもう一度実行 System.out.println("---¥n 登録したコマンドをもう一度実行"); rc.exeReceiver( ); }
//---コマンドインスタンスを作成し,MacroCommand のリスト(history)に登録 [Command パターンに重要]
public static void SetCommand(int number, String str) { Command cmd;
if(number%2==0){
cmd = new BannerCommand1(number, str); // BannerCommand のインスタンス作成
}else{ // (整数と文字列を設定) cmd = new BannerCommand2(number, str); } history.append(cmd); // history(リスト)に登録(追加) cmd.execute(); // 今,設定したコマンドを実行 } //-- 任意の5文字の文字列を作成するメソッド [Command パターンには直接関係ない]
static String generateString(){ int NUM=4;
char[] str= new char[NUM]; for(int i=0; i<NUM-1; i++)
str[i]=(char)((int)(Math.random()*26)+'A'); str[NUM-1]='¥0';
return (new String(str)); // char 配列の文字列を String 型へ変換 }
<実行結果> E:\Command>java Main --- コマンドインスタンスの作成とリストに登録 [奇数用バーナー]---<Banner2>---29:EHI --- [偶数用バーナー]*****<Banner1>***> 28:ZJL <********** [偶数用バーナー]*****<Banner1>***> 8:IFA <********** [偶数用バーナー]*****<Banner1>***> 78:FWF <********** [奇数用バーナー]---<Banner2>---41:EAZ --- --- 登録したコマンドをもう一度実行 [奇数用バーナー]---<Banner2>---29:EHI --- [偶数用バーナー]*****<Banner1>***> 28:ZJL <********** [偶数用バーナー]*****<Banner1>***> 8:IFA <********** [偶数用バーナー]*****<Banner1>***> 78:FWF <********** [奇数用バーナー]---<Banner2>---41:EAZ --- Main MacroCommand history Receiver rc MacroCommand commands execute( ) append( ) undo( ) Command execute( ) BannerCommand1 execute( ) BannerCommand2 execute( ) Receiver MacroCommand history exeReceiver( ) これはMacroCommand のインス タ ン ス で あ り , こ の 中 に コ マ ン ド (Banner Command) のインスタ ンスのリストが存在する こ れ が コ マ ン ド (Banner Command) のインスタンスが格 納されているリスト(Stack) Receiver MacroCommand history exeReceiver( ) history (コマンドのリスト) を共有する History メソッド内のインスタ ンスを(再度)実行するメソッド :Main :Receiver :BannerCommand2 append( ) :BannerCommand1 :MacroCommand new new new new append( ) execute( ) exeRaceiver( ) execute( ) execute( ) execute( ) 個々のコマンド(インスタンス)の 作成と実行 個々のコマンド(インスタンス)の 再実行
Interpreter
特徴
文法規則を表現する
場合に用いられるパターン.
目的に対して,文法表現と,それを利用して文を解釈するインタプリタ機能を供給するパターン
(問題を再帰表現することで,構文のツリー構造 (構文木)
を作成するパターン)
※ 1つのインターフェースより複数のクラスが継承されており,インターフェースを通してクラスの再帰構
造ができている.これにより,継承する複数のクラスは異なるクラス間で関連構造と自分のクラスに再
帰構造を持つ.(Composite パターンに類似)
クラス図
NonterminalExpression childExpressions interpret( ) AbstractExpression interpret( ) TerminalExpression interpret( ) Context getInfoToInterpret( ) Client Uses Creates各クラスの説明
AbstractExpression (抽象的な表現) 構文木のノードに共通のインタフェース(API)を定めるクラス. TerminalExpression (終端となる表現) BNF(Backus−NaurForm,Backus−Normal−Form)による文法構造におけるター ミナル・エクスプレッションに対応する役割をするクラス NonterminalExpression (非終端となる表現) BNF(Backus−NaurForm,Backus−Normal−Form)による文法構造におけるノン ターミナル・エクスプレッションに対応する役割をするクラス AbstractExpression インターフェースと再帰構造を持つことにより,文法構造に単 語の繰り返し表現を実施できる. Context (文脈) インタプリタが構文解析を行なうための情報を提供する役割を持つクラス. Client (依頼者) 構文木を組み立てるために,TerminalExpression や NonterminalExpression を呼び出すクラス. Interpreter パターンを利用するクラスプログラム例
処理命令として「go(1m 直進)」,「right(右 90 回転)」,「left(左 90 回転)」が用意されている AGV を移動させるプロ グラム言語を考える.これに,繰り返しの処理「repeat」を加えて,移動の制御を考える.
プログラムでは 「program」 と 「end」 で処理を挟み,下記の要領で記述する.
program go right right go end 「1m 直進」 → 「右 90 回転」 → 「右 90 回転」 →「1m 直進」 program repeat 4 go right end go end (「1m 直進」 →「右 90 回転」)を4回繰り返す→「1m 直進」 (くり返し処理は 処理 A の繰り返し回数 n に対して repeat と end ではさみ,
“ repeat n 処理 A end ” と記述する)
< Context.java > 構文解析のためのメソッドを提供するクラス(基本的にはパターンに関係ない)
import java.util.*; public class Context {
private String currentToken; // 取得したトークンを格納するための変数 public Context(String text) {
tokenizer = new StringTokenizer(text); nextToken();
}
public String nextToken( ) { // 次のトークンを取得するメソッド if (tokenizer.hasMoreTokens( )) { currentToken = tokenizer.nextToken( ); // 次のトークンを取得する処理 } else { currentToken = null; } return currentToken; }
public String currentToken() { return currentToken; }
public void skipToken(String token) throws ParseException { // 引数のトークンをスキップ(一つ先に進む)
if (!token.equals(currentToken)) {
throw new ParseException("Warning: " + token + " is expected, but " + currentToken + " is found."); }
nextToken( ); // 次のトークンを取得する処理 }
public int currentNumber() throws ParseException { // 現在のトークンの順番号 int number = 0;
try {
number = Integer.parseInt(currentToken); } catch (NumberFormatException e) {
throw new ParseException("Warning: " + e); }
return number; }
}
< Node .java > // Context に対して構文解釈を行なうメソッドを宣言するインターフェース(部品を同一視する)
public abstract class Node {
public abstract void parse(Context context) throws ParseException; }
< ProgramNode .java > //“program”を見つけたら次以降の文字を commandListNode に格納する
// <program> ::= program <command list> public class ProgramNode extends Node {
private Node commandListNode; public void parse(Context context) throws ParseException {
context.skipToken("program"); // ”program”をスキップする(次のトークンへ) commandListNode = new CommandListNode(); // CommandListNode インスタンス作成 commandListNode.parse(context); // context の文字を格納
}
public String toString() {
return "[program " + commandListNode + "]"; }
}
< CommandListNode .java > // 文字列(context)内の単語を分解し commandListNode に格納する
import java.util.Vector;
// <command list> ::= <command>* end
public class CommandListNode extends Node {
private Vector list = new Vector( ); // 分解した文字(単語)を登録するための Vector インスタンス public void parse(Context context) throws ParseException { // 文字列 context から”end”を見つけるまで
while (true) { // 単語を分解して list に格納する
if (context.currentToken() == null) { // context が空の場合 throw new ParseException("Missing 'end'");
context.skipToken("end"); // while 文を抜ける break;
} else {
Node commandNode = new CommandNode(); // 分解した単語は CommandNode
commandNode.parse(context); // インスタンスを作成し,そこに格納する
list.add(commandNode); // そして,list に追加する
} } }
public String toString() { return "" + list; }
}
< CommandNode .java> //くり返し処理,連接処理を判断し,命令の単語(go, right, left)を格納する先を設定する
// <command> ::= <repeat command> | <primitive command> public class CommandNode extends Node {
private Node node;
public void parse(Context context) throws ParseException {
if (context.currentToken().equals("repeat")) { // “repeat” (くり返し処理)を見つけたときの処理
node = new RepeatCommandNode(); // 繰り返し処理のための命令を格納するノードを作成
node.parse(context); // 命令を登録 } else {
node = new PrimitiveCommandNode(); // (連接処理の)命令を格納するノードを作成
node.parse(context); // 命令を登録 }
}
public String toString() { return node.toString(); }
}
< PrimitiveCommandNode .java > // 連接処理のための命令(単語)一つを commandListNode に格納する
// <primitive command> ::= go | right | left
public class PrimitiveCommandNode extends Node {
private String name; //命令一つが入る変数
public void parse(Context context) throws ParseException {
name = context.currentToken(); // context(文字列)のトークンを取得(命令が一つ入る) context.skipToken(name); // context (文字列)内で name をスキップ
if (!name.equals("go") && !name.equals("right") && !name.equals("left")) { throw new ParseException(name + " is undefined");
} }
public String toString() { return name; }
}
< RepeatCommandNode .java > // 繰り返し処理のための命令(単語)を commandListNode に格納する
// <repeat command> ::= repeat <number> <command list> public class RepeatCommandNode extends Node {
private int number; // 繰り返し回数
private Node commandListNode; // 命令の並びを格納するインスタンス
public void parse(Context context) throws ParseException {
context.skipToken("repeat"); // “repeat”を見つけたのでこのメソッドを呼ばれているのでスキップする
number = context.currentNumber(); //“repeat”の後にある数字は「くり返し回数」なので number に格納
context.nextToken(); // 次のトークンへ
commandListNode = new CommandListNode(); //繰り返し処理の命令群を格納するインスタンスを作成
commandListNode.parse(context); //そのインスタンスに context を渡して命令を格納
}
public String toString() {
return "[repeat " + number + " " + commandListNode + "]"; }
< Main .java > Interpreter パターンを利用するクラス import java.util.*;
import java.io.*; public class Main {
public static void main(String[] args) { try {
BufferedReader reader = new BufferedReader(new FileReader("program.txt")); String text;
while ((text = reader.readLine( )) != null) {
System.out.println("text = ¥"" + text + "¥""); Node node = new ProgramNode( );
node.parse(new Context(text)); System.out.println("node = " + node); } } catch (Exception e) { e.printStackTrace(); } } } <実行結果> E:¥>java Main text = "program end" node = [program []] text = "program go end" node = [program [go]]
text = "program go right go right go right go right end" node = [program [go, right, go, right, go, right, go, right]] text = "program repeat 4 go right end end"
node = [program [[repeat 4 [go, right]]]]
text = "program repeat 4 repeat 3 go right go left end right end end" node = [program [[repeat 4 [[repeat 3 [go, right, go, left]], right]]]]
<program.txt> 構文解釈対象のプログラム program end
program go end
program go right go right go right go right end program repeat 4 go right end end
program repeat 4 repeat 3 go right go left end right end end
RepeatCommanNode number commandListNode parse( ) Node parse( ) ProgramNode commandListNode parse( ) Context nextToken( ) currentToken( ) skipToken( ) currentNumber( ) Main Uses Creates CommandListNode list parse( ) CommandNode node parse( ) PrimitiveCommandNode name parse( ) 構文の要素を構成する要素 これらを関連付けて(袋構造にして)構 造木を作成する 構文解析の処理を行なうメソッド program.txt より プログラムを読み込む 1行分の文字列を ProgramNode インスタンスに格納する プログラムの内容 構文解析の結果
RepeatCommanNode number commandListNode parse( ) ProgramNode commandListNode parse( ) CommandNode node parse( ) PrimitiveCommandNode name parse( ) CommandListNode list parse( ) CommandListNode list parse( ) program の単語が見つかった場合 命令の単語をリストとして格納する (リスト用のクラスを利用) 命令の単語をリストとして持つクラス ( list は実際 Vector インスタンス, そ のlist には CommandNode インス タンスが入る) 命令の単語から,繰り返し処理,連 接処理を判断して命令を格納する インスタンスを設定する 命令一つ入る 命令を格納するクラス(構造の端部) 繰り返し処理の場合で,命令を 格納するクラス 繰り返し回数 命令の単語をリストとして格 納する(リスト用のクラスを利用) 構文分析による命令群の保持方法 とそのインスタンスの関係