サイト GOFのパターンについて JavaでHelloWorld デザインパターン デザインパターン Java言語で学ぶデザインパターン入門 デザインパターンの骸骨たち Javaプログラマのためのデザインパターン入門 書籍 『オブジェクト指向における再利用のためのデザインパターン』 『Java言語で学ぶデザインパターン入門』 『Java言語で学ぶデザインパターン入門 マルチスレッド編』 『オブジェクト脳のつくり方―Java・UML・EJBをマスターするための究極の基礎講座 Be Agile!』 『Javaデザインパターン徹底攻略 標準プログラマーズライブラリ』 『独習デザインパターン』
・上位コンセプト(あまりにも用途が具体的過ぎる) Strategy Command State Interpreter ・上位コンセプト(クラス構成の方法であってテクニックではない。自然に使うもの) Factory Method Abstract Factory Builder Proxy Facade Memento Prototype Template Mediator Observer Bridge ・テクニック(役立つものもあるが、あまり使わないものもある) Singleton FlyWeight Adaptor Composite Iterator Visitor Decorator ChainOfResponsibilityこれら23のパターンのうち、特徴的といえるのは最後のものだけで、他は普通にクラス設計してくれば出てくるものであえてパターンと名づけるほどのものではない気がします。SingletonやFlyweightはちょっとしたテクニックであって、パターンというよりもイディオムといった方が適切な気がします。 よく、デザインパターンをそのまま適用できないという意見がありますが、それもそのはずで、挙げられているのはクラス構成における一例にすぎません。無理に当てはめて、﹁このアプリケーションには○○のデザインパターンが使われている﹂などと自慢するのは意味がないし、細かく分析して﹁StrategyとStateの違いはこうである﹂などと研究対象にするのも意味がないと思います。GoFの例に合致していないから効果が薄いと思うのも間違いです。 通常は、以下のようにに23のデザインパターンを分類していますが、少し違った角度から分類したいと思います。
目的 | ||||
生成 | 構造 | 振る舞い | ||
範囲 | クラス | Factory Method | Adapter | Interpreter Template |
オブジェクト | Abstract Factory Builder Prototype Singleton |
Adapter Bridge Composite Decorator Facade Flyweight Proxy |
Chain of Responsibility Command Iterator Mediator Memento Observer State Strategy Visitor |
■私の分類
・ポリモーフィズム(サブクラスによる切り替え) Template Factory Method Strategy Command State Bridge Composite Interpreter ChainOfResponsibility ・仲介 Abstract Factory Builder Facade Mediator ・Wrapper Adapter Proxy Decorator Memento ・機能分散 Observer Visitor Iterator ・インスタンス共有 Singleton FlyWeight Prototype
<まとめ>
ポリモーフィズム | 生成 | 機能分散 | 仲介 | Wrapper | インスタンス保持 | |
Template | ◎ | |||||
Factory Method | ◎ | ○ | ||||
Strategy | ◎ | |||||
Command | ◎ | |||||
State | ◎ | |||||
Composite | ◎ | |||||
Interpreter | ◎ | |||||
Bridge | ◎ | ○ | ||||
ChainOfResponsibility | ◎ | |||||
Abstract Factory | ○ | ○ | ○ | ◎ | ||
Builder | ○ | ○ | ◎ | |||
Facade | ○ | ◎ | ||||
Mediator | ○ | ◎ | ||||
Adaptor | ○ | ◎ | ||||
Proxy | ○ | ◎ | ||||
Decorator | ◎ | |||||
Memento | ○ | ○ | ||||
Observer | ○ | ◎ | ○ | |||
Visitor | ○ | ◎ | ○ | |||
Iterator | ◎ | ○ | ||||
Singleton | ○ | ○ | ○ | |||
FlyWeight | ○ | ○ | ○ | |||
Prototype | ○ | ○ | ○ |
class Hogehoge { void doit() { ... 処理A ... ... 処理B ... ... 処理C ... } }方法としては、 1.if文で分岐させる。 2.サブクラスを作ってポリモーフィズムにする。 が考えられます。 1を適用する場合、
void doit() { ... 処理A ... if (hoge == 1) { ... 処理B ... } else { ... 処理B' ... } ... 処理C ... }のように、if文を追加して処理を分けるでしょう。しかし、この場合、分岐が増えれば増えるほど、その都度、else if文を追加しなければならなくなります。 2の場合は、処理Bのところを、変えて、
void doit() { ... 処理A ... execute(); ... 処理C ... } void abstract execute();として、サブクラスに実装させるようにします。サブクラス側から見れば、それ以外のところは共通処理となっているため、この処理の流れが一つのテンプレートになっています。 実際、設計の段階で最初から、この構造を組み込むことが多く、各処理ごとに実装クラスを分け、親クラスを、
void execute() { init(); inputCheck(); execMain(); } void abstract init(); void abstract inputCheck(); void abstract execMain();として、処理を共通化させます︵この場合、親クラスではメソッドを呼ぶ順序だけ規定して、各メソッドの処理はサブクラスに任せます︶。
class Hogehoge { void doit() { List list = new Vector(); list.add(foo); .... } }このとき、Vectorを使用するかどうか決められない場合、生成メソッドだけにして実装はサブクラスで行います。
abstract class Hogehoge { void doit() { List list = create(); list.add(foo); .... } abstract List create(); } class HogeAList extends Hogehoge { List create() { return new ArrayList(); } }以下の、Strategy、State、Commandは、使われる状況は違いますが、単に切り替わるクラスが戦略なのか、状態なのか、コマンドなのかの違いだけです。これはクラス構成上のテクニックというよりは、何をオブジェクトとして取り出すかという指針に過ぎません。
config property.1=aaa category1 ( property.2=zzz category2 ( property.1=bbb property.2=123 ) ) category3 ( property.2=ccc property.3=456 )これをオブジェクトに取り込むには、
class Config { Category[] category; Property[] property; } class Category { String name; Category[] category; Property[] property; }という感じで、器であるCategoryがCategoryを含むようにすれば無限の階層ができます。これが有効なのは、全体あるいは一部分で一連の処理を行う場合だけで、Category名あるいはProperty名に応じて処理を変える場合は、CompositeやInterpreterパターンの適用は複雑になります。また誤って無限階層を作ってしまうよりは有限の構造にした方が間違いが少なくありません。 まず再帰構造の是非を検討します。ファイルシステムなどの場合で再帰的に処理するのは、リストとしてプリントアウトする場合やファイルサイズを合計したり、コピーするなどの用途です。こういう場合は、パターンを適用して中身と器を同一視するのがいいでしょう。 また、一連の計算や処理を行う場合ですが、例えば、
3 - 5 + 6 * 5 * (6 - 5)という計算をする場合、まず以下のような構文木に分解︵パース︶します。-は値に含め、ノードは+や×のオペレータと値であるオペランドからなります。
interface Node { abstract int getValue(); } class PlusOperand implements Node { Node[] node; int getValue() { int value = 0; for (int i=0;i < node.length;i++) { value += node[i].getValue(); } return value; } } class Operand implements Node { int value; int getValue() { return value; } }
なお、Interpreterはパースのときに使われるパターンではないので、テキストから構文木を作るパース作業は別途行う必要があります。
以上、いくつかのパターンを列挙してきましたが、実際に適用する上で必要な知識は、ポリモーフィズムを使う、というただそれだけのことです。何が切り替えの対象になるかは、その状況状況によって違ってきます。
そのほかのパターンでもポリモーフィズムによる切り替えを基礎にしているパターンは多くありますが、ここに挙げたのはそれが要のものです。
このパターンもサブクラスによる処理の切り替えですが、処理するかどうかをサブクラス自身に判断させているところと、複数のサブクラスに対して順に処理を依頼しているところが、これまでのものと異なっています。
非ポリモーフィックな書き方をすれば、使用するサブクラスの切り替えは以下のようにif-else文かswitch文かを使って行います。
Handler hdr; switch (cnt) { case 0: hdr = new AbcHandler(); case 2: hdr = new XyzHandler(); default: hdr = new DefHandler(); } hdr.handle();ポリモーフィズムの方法からすれば、switch文は排除できるはずですが、以前議論したとおり、どこかで直接指定するか、if文やswitch文で条件判定する必要があります。 Chain Of Responsibilityでは、この条件判定をサブクラス側にさせ、上記のswitch文で取り上げているクラスに順に渡していきます。
class AbcHandler extends Handler { boolean handle(int cnt) { if (cnt != 0) { return false; } else { ... 処理 ... return true; } } }そして、処理できなければ次のサブクラスに回していきます。そして最初に条件に合致したクラスが処理を行い、あとのクラスには回されません。処理の優先順位を決める連鎖リストは呼び出し側で指定する必要があります。 ある条件に対する処理が1対1で決まっている場合、たらい回しにしている分、呼び出しのオーバーヘッドがあります。 別バリエーションとして、最後にデフォルトを持ってきて必ず何らかの処理をさせるパターンや合致する複数の条件すべての処理を可能にするパターンがあります。 Handlerの親クラスおよび実際の処理のサブクラスは変更する必要がなく、別の処理が必要になればサブクラスを追加すればよい点がこのパターンの特徴です。 さらにカスタマイズして、連鎖リストを外部ファイルに記述して読み込むようにすれば、呼び出し側も変更しなくてよくなるでしょう。 ユニークな方法ではありますが、たらい回しにしている分のオーバーヘッドから、あまり使われていません。JavaのAwtのクラスで以前使われていたくらいです。
public class A { protected void exec() { ... 処理 ... } } public class B extends A { public void execA() { super.exec(); } }
public abstract class Border extends Display { protected Display display; protected Border(Display display) { this.display = display; } public String getRowText(int row) { return borderChar + display.getRowText(row) + borderChar; } }
Compositeパターンのみ場合 ――VisitorなしにEntry自身が処理を行う public class File extends Entry { // ... 中略 ... protected void printList(String prefix) { System.out.println(prefix + "/" + this); } } public class Directory extends Entry { // ... 中略 ... protected void printList(String prefix) { System.out.println(prefix + "/" + this); Iterator it = directory.iterator(); while (it.hasNext()) { Entry entry = (Entry)it.next(); entry.printList(prefix + "/" + name); //ポリモーフィズム } } } public class Main { public static void main(String[] args) { Directory rootdir = new Directory("root"); // ... 中略 ... rootdir.printList(); } }
accept()なしの場合 ――VisitorがEntryの処理を行う public class ListVisitor extends Visitor { private String currentdir = ""; public void visit(File file) { System.out.println(currentdir + "/" + file); } public void visit(Directory directory) { System.out.println(currentdir + "/" + directory); String savedir = currentdir; currentdir = currentdir + "/" + directory.getName(); Iterator it = directory.iterator(); while (it.hasNext()) { Entry entry = (Entry)it.next(); if (entry instanceof File) { //非ポリモーフィズム visit((File)entry); } else if (entry instanceof Directory) { visit((Directory)entry); } } currentdir = savedir; } } public class Main { public static void main(String[] args) { Directory rootdir = new Directory("root"); // ... 中略 ... ListVisitor lv = new ListVisitor(); lv.visit(rootdir); } }
accept()ありの場合――EntryがVisitorを訪問させる public class ListVisitor extends Visitor { private String currentdir = ""; public void visit(File file) { System.out.println(currentdir + "/" + file); } public void visit(Directory directory) { System.out.println(currentdir + "/" + directory); String savedir = currentdir; currentdir = currentdir + "/" + directory.getName(); Iterator it = directory.iterator(); while (it.hasNext()) { Entry entry = (Entry)it.next(); entry.accept(this); // ポリモーフィズム } currentdir = savedir; } } public class Directory extends Entry { // ... 中略 ... public void accept(Visitor v) { v.visit(this); } } public class Main { public static void main(String[] args) { Directory rootdir = new Directory("root"); // ... 中略 ... rootdir.accept(new ListVisitor()); } }またこのVisitorパターンではダブルディスパッチが用いられています。 http://java-house.jp/ml/archive/j-h-b/014912.html参照 引数、obj1, obj2, arg1, arg2 で処理されるメソッド︵二つのオブジェクトが特定できた段階で呼ばれるメソッドが特定される︶を 1. obj1.do(obj2, arg1, arg2) 2. obj2.do(obj1, arg1, arg2) のどちらで実装するかというのは、しばしば悩ましい問題です。(obj1, obj2).do(arg1, arg2)のようなダブルディスパッチは、javaでは使えないので、Visitorパターンのようになります。 また以下のページがVisitorパターンについて、具体的なソースを交えて深く突っ込んでいますので参照ください。accept()メソッドなしで非ポリモフィックなinstanceofを使わないやり方が掲載されています。 http://www.ncfreak.com/asato/doc/patterns/visitor.html
サブクラスによって分ける場合 abstract class Animal { abstract int getLeg(); } class Kame extends Animal { int getLeg() { return 4; } } class Tsuru extends Animal { int getLeg() { return 2; } }
インスタンスによって分ける場合 class Animal { String type; Animal(String type) { this.type = type; } int getLeg() { if (type.equals("Kame")) { return 4; } else if (type.equals("Tsuru")) { return 2; } } }