デザインパターン 入門
デザインパターン
デザインパターン 入門
Basic
Iterator : 1つ1つ数え上げる
Adapter : 一皮かぶせて再利用
サブクラス 3. Tempalte Method : 具体的な処理をサブクラスにまかせる 4. Factory Method : インスタンス作成をサブクラスにまかせる
インスタンス生成 5. Singleton : たった1つのインスタンス 6. Prototype : コピーしてインスタンスを作る 7. Builder : 複雑なインスタンスを組み立てる 8. Abstract Factory : 関連する部品を組み合わせて製品を作る
分離 9. Bridge : 機能の階層と実装の階層を分ける 10. Strategy : アルゴリズムをごっそり切り替える
同一視 11. Composite : 容器と中身の同一視 12. Decorator : 飾り枠と中身の同一視
構造間の移動 13. Visitor : 構造を渡り歩きながら仕事をする 14. Chain of Responsibility : 責任のたらい回し
Simplifying 15. Facade : シンプルな窓口 16. Mediator : 相手は相談役1人だけ
状態管理 17. Observer : 状態の変化を通知する 18. Memento : 状態を保存する 19. State : 状態をクラスとして表現する
無駄をなくす 20. Flyweight : 同じものを共有して無駄をなくす 21. Proxy : 必要になってから作る
クラスで表現 22. Command : 命令をクラスにする 23. Interpreter : 文法規則をクラスで表現する
GoFによるデザインパターンの分類
生成に関するパターン
Abstract Factory
Factory Method
Singleton
Builder
Prototype
構造に関するパターン
Adapter
Composite
Facade
Proxy
Bridge
Decorator
Flyweight
振る舞いに関するパターン
Chain of Responsibility
Interpreter
Mediator
Observer
Strategy
Visitor
Command
Iterator
Memento
State
Template Method
設計に向けて
共通性/可変性分析
分析マトリクス
Details
以下、記載のソースコードは増補改訂版 Java言語で学ぶ デザインパターン入門のURLでThe zlib/libpng Licenseとして配布されているものから、コメントアウトを取り除いたものとして利用させていただいています。
1. Iterator
何かがたくさん集まっているときに、それを順番に指し示していき、全体をスキャンしていく処理を行うためのもの
実装と切り離して繰り返しを行える。使用するのはIteratorのhasNextとnextメソッドのみ。
実装
Iteratorの宣言を含む集合体
public class BookShelf implements Aggregate {
private Book[] books;
private int last = 0;
public BookShelf(int maxsize) {
this.books = new Book[maxsize];
}
public Book getBookAt(int index) {
return books[index];
}
public void appendBook(Book book) {
this.books[last] = book;
last++;
}
public int getLength() {
return last;
}
public Iterator iterator() {
return new BookShelfIterator(this);
}
}
public interface Aggregate {
public abstract Iterator iterator();
}
Iteratorの実装
public class BookShelfIterator implements Iterator {
private BookShelf bookShelf;
private int index;
public BookShelfIterator(BookShelf bookShelf) {
this.bookShelf = bookShelf;
this.index = 0;
}
public boolean hasNext() {
if (index < bookShelf.getLength()) {
return true;
} else {
return false;
}
}
public Object next() {
Book book = bookShelf.getBookAt(index);
index++;
return book;
}
}
public interface Iterator {
public abstract boolean hasNext();
public abstract Object next();
}
使用方法
import java.util.*;
public class Main {
public static void main(String[] args) {
BookShelf bookShelf = new BookShelf(4);
bookShelf.appendBook(new Book("Around the World in 80 Days"));
bookShelf.appendBook(new Book("Bible"));
bookShelf.appendBook(new Book("Cinderella"));
bookShelf.appendBook(new Book("Daddy-Long-Legs"));
Iterator it = bookShelf.iterator();
while (it.hasNext()) {
Book book = (Book)it.next();
System.out.println(book.getName());
}
}
}
2. Adapter
すでに提供されているものがそのまま使えず、wrappterして必要な形に変換して利用
以下の2種類
継承
委譲
継承
元々定義されている実装
public class Banner {
private String string;
public Banner(String string) {
this.string = string;
}
public void showWithParen() {
System.out.println("(" + string + ")");
}
public void showWithAster() {
System.out.println("*" + string + "*");
}
}
使用したいインターフェイス
public interface Print {
public abstract void printWeak();
public abstract void printStrong();
}
ここで以下のような継承したクラスを実装
public class PrintBanner extends Banner implements Print {
public PrintBanner(String string) {
super(string);
}
public void printWeak() {
showWithParen();
}
public void printStrong() {
showWithAster();
}
}
委譲
printは抽象クラスとして定義しておく。それ以外は基本同じ。PrintBannerクラスからBannerクラスのオブジェクトを生成し、同クラス内の関数を呼び出す。
public class PrintBanner extends Print {
private Banner banner;
public PrintBanner(String string) {
this.banner = new Banner(string);
}
public void printWeak() {
banner.showWithParen();
}
public void printStrong() {
banner.showWithAster();
}
}
3. Template Method
スーパークラスで処理の枠組みを定め、サブクラスでその具体的な内容を定義
複数のクラス間でロジックの抽象化
サブクラスをスーパークラスと同一視可能
スーパークラス型の変数に、サブクラスのインスタンスのどれを代入しても正しく動作するようにする
The Liskov Substition Principle(LSP)
抽象化クラス
public abstract class AbstractDisplay {
public abstract void open();
public abstract void print();
public abstract void close();
public final void display() {
open();
for (int i = 0; i < 5; i++) {
print();
}
close();
}
}
具象クラス1
public class CharDisplay extends AbstractDisplay {
private char ch;
public CharDisplay(char ch) {
this.ch = ch;
}
public void open() {
System.out.print("<<");
}
public void print() {
System.out.print(ch);
}
public void close() {
System.out.println(">>");
}
}
具象クラス2
public class StringDisplay extends AbstractDisplay {
private String string;
private int width;
public StringDisplay(String string) {
this.string = string;
this.width = string.getBytes().length;
}
public void open() {
printLine();
}
public void print() {
System.out.println("|" + string + "|");
}
public void close() {
printLine();
}
private void printLine() {
System.out.print("+");
for (int i = 0; i < width; i++) {
System.out.print("-");
}
System.out.println("+");
}
}
使用方法
public class Main {
public static void main(String[] args) {
AbstractDisplay d1 = new CharDisplay('H');
AbstractDisplay d2 = new StringDisplay("Hello, world.");
AbstractDisplay d3 = new StringDisplay("こんにちは。");
d1.display();
d2.display();
d3.display();
}
}
4. Factory Method
インスタンスを生成する工場をTemplate Methodパターンで構成
インスタンスの作り方をスーパークラスの側で定める
抽象化した工場
package framework;
public abstract class Factory {
public final Product create(String owner) {
Product p = createProduct(owner);
registerProduct(p);
return p;
}
protected abstract Product createProduct(String owner);
protected abstract void registerProduct(Product product);
}
抽象化した製造物
package framework;
public abstract class Product {
public abstract void use();
}
具象化した工場
package idcard;
import framework.*;
import java.util.*;
public class IDCardFactory extends Factory {
private List owners = new ArrayList();
protected Product createProduct(String owner) {
return new IDCard(owner);
}
protected void registerProduct(Product product) {
owners.add(((IDCard)product).getOwner());
}
public List getOwners() {
return owners;
}
}
具象化した製造物
package idcard;
import framework.*;
public class IDCard extends Product {
private String owner;
IDCard(String owner) {
System.out.println(owner + "のカードを作ります。");
this.owner = owner;
}
public void use() {
System.out.println(owner + "のカードを使います。");
}
public String getOwner() {
return owner;
}
}
使用方法
import framework.*;
import idcard.*;
public class Main {
public static void main(String[] args) {
Factory factory = new IDCardFactory();
Product card1 = factory.create("·ë¾ë¹À");
Product card2 = factory.create("¤È¤à¤é");
Product card3 = factory.create("º´Æ£²Ö»Ò");
card1.use();
card2.use();
card3.use();
}
}
抽象化した工場におけるメソッドの実装方法
抽象化メソッド
デフォルトの実装
例外スロー
5. Singleton
システムの中に1個しか存在しないものをプログラムで表現したいとき
以下の場合、getInstance()メソッドを呼び出した際に暮らすが初期化されて、そのときにstaticフィールドの初期化が行われる。
public class Singleton {
private static Singleton singleton = new Singleton();
private Singleton() {
System.out.println("インスタンスを生成しました。");
}
public static Singleton getInstance() {
return singleton;
}
}
デメリット
ユニットテストがしづらい
複数並列して行われる場合にはシングルトンは使えないかも
6. Prototype
new Something()
のようにクラスからインスタンスを作るのではなく、インスタンスを複製して新しいインスタンスを作る例えば、以下のような状況
種類が多すぎてクラスにまとめられない場合
クラスからのインスタンス生成が難しい場合
フレームワークと生成するインスタンスを分けたい場合
複製される対象のクラスはCloneableインターフェイスを実装する必要があります。 このCloneableインターフェイスは、マーカーインターフェイスと呼ばれるもので、メソッドが一つも定義されておらず、cloneによってコピーすることができるという印のみに使用されます。Cloneableインターフェイスを実装しない場合、CloneNotSupportedExceptionの例外が発生します。
Cloneableインターフェイスを継承したインターフェイスを用意。実際には、コピー対象のクラスやそのスーパークラスで直接実装しても問題ありません。
package framework;
import java.lang.Cloneable;
public interface Product extends Cloneable {
public abstract void use(String s);
public abstract Product createClone();
}
import framework.*;
public class MessageBox implements Product {
private char decochar;
public MessageBox(char decochar) {
this.decochar = decochar;
}
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("");
}
public Product createClone() {
Product p = null;
try {
p = (Product)clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return p;
}
}
Managerクラスの実装
package framework;
import java.util.*;
public class Manager {
private HashMap showcase = new HashMap();
public void register(String name, Product proto) {
showcase.put(name, proto);
}
public Product create(String protoname) {
Product p = (Product)showcase.get(protoname);
return p.createClone();
}
}
使用方法
import framework.*;
public class Main {
public static void main(String[] args) {
Manager manager = new Manager();
UnderlinePen upen = new UnderlinePen('~');
MessageBox mbox = new MessageBox('*');
MessageBox sbox = new MessageBox('/');
manager.register("strong message", upen);
manager.register("warning box", mbox);
manager.register("slash box", sbox);
Product p1 = manager.create("strong message");
p1.use("Hello, world.");
Product p2 = manager.create("warning box");
p2.use("Hello, world.");
Product p3 = manager.create("slash box");
p3.use("Hello, world.");
}
}
7. Builder
Builderクラスは、インスタンスを作成するためのインターフェイスを定義し、そこからそれを実装した各種具象クラスを用意し、Director役のクラスがそれらの具象クラスのメソッドを組み合わせて、目的のものを作り上げる。
Builderクラス
public abstract class Builder {
public abstract void makeTitle(String title);
public abstract void makeString(String str);
public abstract void makeItems(String[] items);
public abstract void close();
}
具象クラスの例
import java.io.*;
public class HTMLBuilder extends Builder {
private String filename;
private PrintWriter writer;
public void makeTitle(String title) {
filename = title + ".html";
try {
writer = new PrintWriter(new FileWriter(filename));
} catch (IOException e) {
e.printStackTrace();
}
writer.println("<html><head><title>" + title + "</title></head><body>");
writer.println("<h1>" + title + "</h1>");
}
public void makeString(String str) {
writer.println("<p>" + str + "</p>");
}
public void makeItems(String[] items) {
writer.println("<ul>");
for (int i = 0; i < items.length; i++) {
writer.println("<li>" + items[i] + "</li>");
}
writer.println("</ul>");
}
public void close() {
writer.println("</body></html>");
writer.close();
}
public String getResult() {
return filename;
}
}
Directorクラスで具象クラスのメソッドを組み合わせて目的のものを作り上げる。
public class Director {
private Builder builder;
public Director(Builder builder) {
this.builder = builder;
}
public void construct() {
builder.makeTitle("Greeting");
builder.makeString("朝から昼にかけて");
builder.makeItems(new String[]{
"おはようございます。",
"こんにちは。",
});
builder.makeString("夜に");
builder.makeItems(new String[]{
"こんばんは。",
"おやすみなさい。",
"さようなら。",
});
builder.close();
}
}
使用方法
public class Main {
public static void main(String[] args) {
if (args.length != 1) {
usage();
System.exit(0);
}
if (args[0].equals("plain")) {
TextBuilder textbuilder = new TextBuilder();
Director director = new Director(textbuilder);
director.construct();
String result = textbuilder.getResult();
System.out.println(result);
} else if (args[0].equals("html")) {
HTMLBuilder htmlbuilder = new HTMLBuilder();
Director director = new Director(htmlbuilder);
director.construct();
String filename = htmlbuilder.getResult();
System.out.println(filename + "が作成されました。");
} else {
usage();
System.exit(0);
}
}
public static void usage() {
System.out.println("Usage: java Main plain プレーンテキストで文書作成");
System.out.println("Usage: java Main html HTMLファイルで文書作成");
}
}
8. Abstract Factory
抽象的な工場で、抽象的な部品を組み合わせて抽象的な製品を作る
部分の具体的な実装には着目せず、インターフェースに着目し、そのインターフェイスだけを使って、部品を組み立て、製品にまとめる
工場の呼び出し元では、具体的な部品、製品、工場等は利用せず、
java Main listfactory.ListFactory
コマンドのように呼び出すクラスを引数で与えており、Class.forName(classname).newInstance()
のように生成
抽象的な工場
package factory;
public abstract class Factory {
public static Factory getFactory(String classname) {
Factory factory = null;
try {
factory = (Factory)Class.forName(classname).newInstance();
} catch (ClassNotFoundException e) {
System.err.println("クラス " + classname + " が見つかりません。");
} catch (Exception e) {
e.printStackTrace();
}
return factory;
}
public abstract Link createLink(String caption, String url);
public abstract Tray createTray(String caption);
public abstract Page createPage(String title, String author);
}
抽象化部品の例
package factory;
public abstract class Link extends Item {
protected String url;
public Link(String caption, String url) {
super(caption);
this.url = url;
}
}
具体的な工場
package listfactory;
import factory.*;
public class ListFactory extends Factory {
public Link createLink(String caption, String url) {
return new ListLink(caption, url);
}
public Tray createTray(String caption) {
return new ListTray(caption);
}
public Page createPage(String title, String author) {
return new ListPage(title, author);
}
}
具体的な部品の例
package listfactory;
import factory.*;
public class ListLink extends Link {
public ListLink(String caption, String url) {
super(caption, url);
}
public String makeHTML() {
return " <li><a href=\"" + url + "\">" + caption + "</a></li>\n";
}
}
呼び出し元
import factory.*;
public class Main {
public static void main(String[] args) {
if (args.length != 1) {
System.out.println("Usage: java Main class.name.of.ConcreteFactory");
System.out.println("Example 1: java Main listfactory.ListFactory");
System.out.println("Example 2: java Main tablefactory.TableFactory");
System.exit(0);
}
Factory factory = Factory.getFactory(args[0]);
Link asahi = factory.createLink("朝日新聞", "http:
Link yomiuri = factory.createLink("読売新聞", "http:
Link us_yahoo = factory.createLink("Yahoo!", "http:
Link jp_yahoo = factory.createLink("Yahoo!Japan", "http:
Link excite = factory.createLink("Excite", "http:
Link google = factory.createLink("Google", "http:
Tray traynews = factory.createTray("新聞");
traynews.add(asahi);
traynews.add(yomiuri);
Tray trayyahoo = factory.createTray("Yahoo!");
trayyahoo.add(us_yahoo);
trayyahoo.add(jp_yahoo);
Tray traysearch = factory.createTray("サーチエンジン");
traysearch.add(trayyahoo);
traysearch.add(excite);
traysearch.add(google);
Page page = factory.createPage("LinkPage", "結城 浩");
page.add(traynews);
page.add(traysearch);
page.output();
}
}
クラスの生成方法
new :
Somethine obj = new Something();
clone :
obj = (Something)clone();
newInstance :
(Something) Class.forName(classname).newInstance();
,someobj.getClass().newInstance()
メリット/デメリット
メリット : 具体的な工場を新たに追加するのは簡単
デメリット: 部品を新たに追加するのは困難
9. Bridge
機能のクラス階層と実装のクラス階層をわけることで、それぞれのクラス階層を独立に拡張できる
機能の抽象クラス
public class Display {
private DisplayImpl impl;
public Display(DisplayImpl impl) {
this.impl = impl;
}
public void open() {
impl.rawOpen();
}
public void print() {
impl.rawPrint();
}
public void close() {
impl.rawClose();
}
public final void display() {
open();
print();
close();
}
}
機能の具象クラス
public class CountDisplay extends Display {
public CountDisplay(DisplayImpl impl) {
super(impl);
}
public void multiDisplay(int times) {
open();
for (int i = 0; i < times; i++) {
print();
}
close();
}
}
実装の抽象クラス
public abstract class DisplayImpl {
public abstract void rawOpen();
public abstract void rawPrint();
public abstract void rawClose();
}
実装の具象クラス
public class StringDisplayImpl extends DisplayImpl {
private String string;
private int width;
public StringDisplayImpl(String string) {
this.string = string;
this.width = string.getBytes().length;
}
public void rawOpen() {
printLine();
}
public void rawPrint() {
System.out.println("|" + string + "|");
}
public void rawClose() {
printLine();
}
private void printLine() {
System.out.print("+");
for (int i = 0; i < width; i++) {
System.out.print("-");
}
System.out.println("+");
}
}
使用方法
public class Main {
public static void main(String[] args) {
Display d1 = new Display(new StringDisplayImpl("Hello, Japan."));
Display d2 = new CountDisplay(new StringDisplayImpl("Hello, World."));
CountDisplay d3 = new CountDisplay(new StringDisplayImpl("Hello, Universe."));
d1.display();
d2.display();
d3.display();
d3.multiDisplay(5);
}
}
10. Strategy
アルゴリズムを切り替え、同じ問題を別の方で解くのを容易にするパターン
委譲というゆるやかな結びつきでアルゴリズムの切り替えが容易
Strategy役の抽象クラス
public interface Strategy {
public abstract Hand nextHand();
public abstract void study(boolean win);
}
Strategy役の具象クラス
import java.util.Random;
public class ProbStrategy implements Strategy {
private Random random;
private int prevHandValue = 0;
private int currentHandValue = 0;
private int[][] history = {
{ 1, 1, 1, },
{ 1, 1, 1, },
{ 1, 1, 1, },
};
public ProbStrategy(int seed) {
random = new Random(seed);
}
public Hand nextHand() {
int bet = random.nextInt(getSum(currentHandValue));
int handvalue = 0;
if (bet < history[currentHandValue][0]) {
handvalue = 0;
} else if (bet < history[currentHandValue][0] + history[currentHandValue][1]) {
handvalue = 1;
} else {
handvalue = 2;
}
prevHandValue = currentHandValue;
currentHandValue = handvalue;
return Hand.getHand(handvalue);
}
private int getSum(int hv) {
int sum = 0;
for (int i = 0; i < 3; i++) {
sum += history[hv][i];
}
return sum;
}
public void study(boolean win) {
if (win) {
history[prevHandValue][currentHandValue]++;
} else {
history[prevHandValue][(currentHandValue + 1) % 3]++;
history[prevHandValue][(currentHandValue + 2) % 3]++;
}
}
}
Strategy役を利用するクラス
public class Main {
public static void main(String[] args) {
if (args.length != 2) {
System.out.println("Usage: java Main randomseed1 randomseed2");
System.out.println("Example: java Main 314 15");
System.exit(0);
}
int seed1 = Integer.parseInt(args[0]);
int seed2 = Integer.parseInt(args[1]);
Player player1 = new Player("Taro", new WinningStrategy(seed1));
Player player2 = new Player("Hana", new ProbStrategy(seed2));
for (int i = 0; i < 10000; i++) {
Hand nextHand1 = player1.nextHand();
Hand nextHand2 = player2.nextHand();
if (nextHand1.isStrongerThan(nextHand2)) {
System.out.println("Winner:" + player1);
player1.win();
player2.lose();
} else if (nextHand2.isStrongerThan(nextHand1)) {
System.out.println("Winner:" + player2);
player1.lose();
player2.win();
} else {
System.out.println("Even...");
player1.even();
player2.even();
}
}
System.out.println("Total result:");
System.out.println(player1.toString());
System.out.println(player2.toString());
}
}
11. Composite
複数と単数を同一視して、再帰的な構造を作る
Component役となるEntryという抽象クラスで、FileクラスとDirectoryクラスを同一視する。
public abstract class Entry {
public abstract String getName();
public abstract int getSize();
public Entry add(Entry entry) throws FileTreatmentException {
throw new FileTreatmentException();
}
public void printList() {
printList("");
}
protected abstract void printList(String prefix);
public String toString() {
return getName() + " (" + getSize() + ")";
}
}
単数となるFileクラス
public class File extends Entry {
private String name;
private int size;
public File(String name, int size) {
this.name = name;
this.size = size;
}
public String getName() {
return name;
}
public int getSize() {
return size;
}
protected void printList(String prefix) {
System.out.println(prefix + "/" + this);
}
}
複数となるDirectoryクラス
import java.util.Iterator;
import java.util.ArrayList;
public class Directory extends Entry {
private String name;
private ArrayList directory = new ArrayList();
public Directory(String name) {
this.name = name;
}
public String getName() {
return name;
}
public int getSize() {
int size = 0;
Iterator it = directory.iterator();
while (it.hasNext()) {
Entry entry = (Entry)it.next();
size += entry.getSize();
}
return size;
}
public Entry add(Entry entry) {
directory.add(entry);
return this;
}
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) {
try {
System.out.println("Making root entries...");
Directory rootdir = new Directory("root");
Directory bindir = new Directory("bin");
Directory tmpdir = new Directory("tmp");
Directory usrdir = new Directory("usr");
rootdir.add(bindir);
rootdir.add(tmpdir);
rootdir.add(usrdir);
bindir.add(new File("vi", 10000));
bindir.add(new File("latex", 20000));
rootdir.printList();
System.out.println("");
System.out.println("Making user entries...");
Directory yuki = new Directory("yuki");
Directory hanako = new Directory("hanako");
Directory tomura = new Directory("tomura");
usrdir.add(yuki);
usrdir.add(hanako);
usrdir.add(tomura);
yuki.add(new File("diary.html", 100));
yuki.add(new File("Composite.java", 200));
hanako.add(new File("memo.tex", 300));
tomura.add(new File("game.doc", 400));
tomura.add(new File("junk.mail", 500));
rootdir.printList();
} catch (FileTreatmentException e) {
e.printStackTrace();
}
}
}
12. Decorator
中心となるオブジェクトがあり、それに飾り付けとなる機能を一皮一皮かぶせていって、より目的にあったオブジェクトに仕上げていく
飾り枠と中身を同一しており、透過的なインターフェイスを持つ
中身を変えずに機能追加ができる。
呼び出し元
public class Main {
public static void main(String[] args) {
Display b1 = new StringDisplay("Hello, world.");
Display b2 = new SideBorder(b1, '#');
Display b3 = new FullBorder(b2);
b1.show();
b2.show();
b3.show();
Display b4 =
new SideBorder(
new FullBorder(
new FullBorder(
new SideBorder(
new FullBorder(
new StringDisplay("こんにちは。")
),
'*'
)
)
),
'/'
);
b4.show();
}
}
中心となるオブジェクトの元になる抽象化されたクラス
public abstract class Display {
public abstract int getColumns();
public abstract int getRows();
public abstract String getRowText(int row);
public void show() {
for (int i = 0; i < getRows(); i++) {
System.out.println(getRowText(i));
}
}
}
中心となるオブジェクトの元になる具象化されたクラス
public class StringDisplay extends Display {
private String string;
public StringDisplay(String string) {
this.string = string;
}
public int getColumns() {
return string.getBytes().length;
}
public int getRows() {
return 1;
}
public String getRowText(int row) {
if (row == 0) {
return string;
} else {
return null;
}
}
}
飾り付けの元になる抽象化クラス
public abstract class Border extends Display {
protected Display display;
protected Border(Display display) {
this.display = display;
}
}
飾り付けの元になる具象化クラス
public class SideBorder extends Border {
private char borderChar;
public SideBorder(Display display, char ch) {
super(display);
this.borderChar = ch;
}
public int getColumns() {
return 1 + display.getColumns() + 1;
}
public int getRows() {
return display.getRows();
}
public String getRowText(int row) {
return borderChar + display.getRowText(row) + borderChar;
}
}
13. Visitor
データ構造と処理を分離
データ構造の中を巡り歩く主体である訪問者を表すクラスを用意し、そのクラスに処理を任せる
ConcreateElement役とConcreteVisitor役の組によって実際の処理が決定するというダブルディスパッチ。それぞれのクラスで互いが互いを呼び出している。
抽象化されたVisitor役
public abstract class Visitor {
public abstract void visit(File file);
public abstract void visit(Directory directory);
}
具象化されたVisitor役
import java.util.Iterator;
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 interface Element {
public abstract void accept(Visitor v);
}
抽象クラスで実装を宣言
import java.util.Iterator;
public abstract class Entry implements Element {
public abstract String getName();
public abstract int getSize();
public Entry add(Entry entry) throws FileTreatmentException {
throw new FileTreatmentException();
}
public Iterator iterator() throws FileTreatmentException {
throw new FileTreatmentException();
}
public String toString() {
return getName() + " (" + getSize() + ")";
}
}
ただし実際には具象クラスで定義
public class File extends Entry {
private String name;
private int size;
public File(String name, int size) {
this.name = name;
this.size = size;
}
public String getName() {
return name;
}
public int getSize() {
return size;
}
public void accept(Visitor v) {
v.visit(this);
}
}
import java.util.Iterator;
import java.util.ArrayList;
public class Directory extends Entry {
private String name;
private ArrayList dir = new ArrayList();
public Directory(String name) {
this.name = name;
}
public String getName() {
return name;
}
public int getSize() {
int size = 0;
Iterator it = dir.iterator();
while (it.hasNext()) {
Entry entry = (Entry)it.next();
size += entry.getSize();
}
return size;
}
public Entry add(Entry entry) {
dir.add(entry);
return this;
}
public Iterator iterator() {
return dir.iterator();
}
public void accept(Visitor v) {
v.visit(this);
}
}
呼び出し元
public class Main {
public static void main(String[] args) {
try {
System.out.println("Making root entries...");
Directory rootdir = new Directory("root");
Directory bindir = new Directory("bin");
Directory tmpdir = new Directory("tmp");
Directory usrdir = new Directory("usr");
rootdir.add(bindir);
rootdir.add(tmpdir);
rootdir.add(usrdir);
bindir.add(new File("vi", 10000));
bindir.add(new File("latex", 20000));
rootdir.accept(new ListVisitor());
System.out.println("");
System.out.println("Making user entries...");
Directory yuki = new Directory("yuki");
Directory hanako = new Directory("hanako");
Directory tomura = new Directory("tomura");
usrdir.add(yuki);
usrdir.add(hanako);
usrdir.add(tomura);
yuki.add(new File("diary.html", 100));
yuki.add(new File("Composite.java", 200));
hanako.add(new File("memo.tex", 300));
tomura.add(new File("game.doc", 400));
tomura.add(new File("junk.mail", 500));
rootdir.accept(new ListVisitor());
} catch (FileTreatmentException e) {
e.printStackTrace();
}
}
}
OCP : Open-Closed Principle
クラスなどが以下の原則に従うべき
拡張については開かれているが、
修正については閉じられている
既存のクラスを修正せずに拡張できるようにせよ
14. Chain of Responsibility
複数のオブジェクトをチェーンのようにつないでおき、そのオブジェクトの鎖を順次渡り歩いて、目的のオブジェクトを決定する方法
要求する側と処理する側の結びつきを弱めることができ、それぞれを部品として独立させることができる
状況によって要求を処理するオブジェクトが変化するようなプログラムにも対応することができる
使用方法
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));
}
}
}
抽象化クラス。support()中のnext.support(trouble);
で次に人に処理を渡す。
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 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.");
}
}
15. Facade
関係し合っているたくさんのクラスを適切に制御するために、それらのクラスを個別に制御しなくても、窓口に対して要求を出すだけですむように新たにシンプルなインターフェースを用意する
システムの内側にある各クラスの役割や依存関係を考えて、正しい順番でクラスを利用
インターフェースを少なくする
Facade役の実装
package pagemaker;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Properties;
public class PageMaker {
private PageMaker() {
}
public static void makeWelcomePage(String mailaddr, String filename) {
try {
Properties mailprop = Database.getProperties("maildata");
String username = mailprop.getProperty(mailaddr);
HtmlWriter writer = new HtmlWriter(new FileWriter(filename));
writer.title("Welcome to " + username + "'s page!");
writer.paragraph(username + "のページへようこそ。");
writer.paragraph("メールまっていますね。");
writer.mailto(mailaddr, username);
writer.close();
System.out.println(filename + " is created for " + mailaddr + " (" + username + ")");
} catch (IOException e) {
e.printStackTrace();
}
}
}
個々の実装1
package pagemaker;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Properties;
public class Database {
private Database() {
}
public static Properties getProperties(String dbname) {
String filename = dbname + ".txt";
Properties prop = new Properties();
try {
prop.load(new FileInputStream(filename));
} catch (IOException e) {
System.out.println("Warning: " + filename + " is not found.");
}
return prop;
}
}
個々の実装2
package pagemaker;
import java.io.Writer;
import java.io.IOException;
public class HtmlWriter {
private Writer writer;
public HtmlWriter(Writer writer) {
this.writer = writer;
}
public void title(String title) throws IOException {
writer.write("<html>");
writer.write("<head>");
writer.write("<title>" + title + "</title>");
writer.write("</head>");
writer.write("<body>\n");
writer.write("<h1>" + title + "</h1>\n");
}
public void paragraph(String msg) throws IOException {
writer.write("<p>" + msg + "</p>\n");
}
public void link(String href, String caption) throws IOException {
paragraph("<a href=\"" + href + "\">" + caption + "</a>");
}
public void mailto(String mailaddr, String username) throws IOException {
link("mailto:" + mailaddr, username);
}
public void close() throws IOException {
writer.write("</body>");
writer.write("</html>\n");
writer.close();
}
}
利用方法
import pagemaker.PageMaker;
public class Main {
public static void main(String[] args) {
PageMaker.makeWelcomePage("hyuki@hyuki.com", "welcome.html");
}
}
16. Mediator
各メンバーはみんな相談役だけに報告し、メンバーへの支持は相談役だけから来るようにする
多数のオブジェクト間の調整を行わなければならないときこそ、Mediatorパターンの出番
ConcreteColleague役は再利用しやすいが、ConcreteMediator役は再利用しにくい
呼び出し元
import java.awt.*;
import java.awt.event.*;
public class Main {
static public void main(String args[]) {
new LoginFrame("Mediator Sample");
}
}
抽象化されたMediatorクラス
public interface Mediator {
public abstract void createColleagues();
public abstract void colleagueChanged();
}
具象化したMediatorクラス
import java.awt.Frame;
import java.awt.Label;
import java.awt.Color;
import java.awt.CheckboxGroup;
import java.awt.GridLayout;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
public class LoginFrame extends Frame implements ActionListener, Mediator {
private ColleagueCheckbox checkGuest;
private ColleagueCheckbox checkLogin;
private ColleagueTextField textUser;
private ColleagueTextField textPass;
private ColleagueButton buttonOk;
private ColleagueButton buttonCancel;
public LoginFrame(String title) {
super(title);
setBackground(Color.lightGray);
setLayout(new GridLayout(4, 2));
createColleagues();
add(checkGuest);
add(checkLogin);
add(new Label("Username:"));
add(textUser);
add(new Label("Password:"));
add(textPass);
add(buttonOk);
add(buttonCancel);
colleagueChanged();
pack();
show();
}
public void createColleagues() {
CheckboxGroup g = new CheckboxGroup();
checkGuest = new ColleagueCheckbox("Guest", g, true);
checkLogin = new ColleagueCheckbox("Login", g, false);
textUser = new ColleagueTextField("", 10);
textPass = new ColleagueTextField("", 10);
textPass.setEchoChar('*');
buttonOk = new ColleagueButton("OK");
buttonCancel = new ColleagueButton("Cancel");
checkGuest.setMediator(this);
checkLogin.setMediator(this);
textUser.setMediator(this);
textPass.setMediator(this);
buttonOk.setMediator(this);
buttonCancel.setMediator(this);
checkGuest.addItemListener(checkGuest);
checkLogin.addItemListener(checkLogin);
textUser.addTextListener(textUser);
textPass.addTextListener(textPass);
buttonOk.addActionListener(this);
buttonCancel.addActionListener(this);
}
public void colleagueChanged() {
if (checkGuest.getState()) {
textUser.setColleagueEnabled(false);
textPass.setColleagueEnabled(false);
buttonOk.setColleagueEnabled(true);
} else {
textUser.setColleagueEnabled(true);
userpassChanged();
}
}
private void userpassChanged() {
if (textUser.getText().length() > 0) {
textPass.setColleagueEnabled(true);
if (textPass.getText().length() > 0) {
buttonOk.setColleagueEnabled(true);
} else {
buttonOk.setColleagueEnabled(false);
}
} else {
textPass.setColleagueEnabled(false);
buttonOk.setColleagueEnabled(false);
}
}
public void actionPerformed(ActionEvent e) {
System.out.println(e.toString());
System.exit(0);
}
}
抽象化したColleagueクラス
public interface Colleague {
public abstract void setMediator(Mediator mediator);
public abstract void setColleagueEnabled(boolean enabled);
}
具象化したColleaqueクラス
import java.awt.Button;
public class ColleagueButton extends Button implements Colleague {
private Mediator mediator;
public ColleagueButton(String caption) {
super(caption);
}
public void setMediator(Mediator mediator) {
this.mediator = mediator;
}
public void setColleagueEnabled(boolean enabled) {
setEnabled(enabled);
}
}
17. Observer
観察対象の状態が変化すると、観察者に対して通知。状態変化に応じた処理を記述するときに有効
Observer役の処理がSubject役の処理に影響を与えるとき、無限連鎖に注意して設計。Observer役に、現在Subject役から通知されている最中かどうかを表すフラグ変数を一つ持たせるのがよい
抽象化したObserver役のクラス
public interface Observer {
public abstract void update(NumberGenerator generator);
}
具象化したObserver役のクラス
public class DigitObserver implements Observer {
public void update(NumberGenerator generator) {
System.out.println("DigitObserver:" + generator.getNumber());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
}
}
}
抽象化したSubject役のクラス
import java.util.ArrayList;
import java.util.Iterator;
public abstract class NumberGenerator {
private ArrayList observers = new ArrayList();
public void addObserver(Observer observer) {
observers.add(observer);
}
public void deleteObserver(Observer observer) {
observers.remove(observer);
}
public void notifyObservers() {
Iterator it = observers.iterator();
while (it.hasNext()) {
Observer o = (Observer)it.next();
o.update(this);
}
}
public abstract int getNumber();
public abstract void execute();
}
具象化したSubject役のクラス
import java.util.Random;
public class RandomNumberGenerator extends NumberGenerator {
private Random random = new Random();
private int number;
public int getNumber() {
return number;
}
public void execute() {
for (int i = 0; i < 20; i++) {
number = random.nextInt(50);
notifyObservers();
}
}
}
呼び出し元
public class Main {
public static void main(String[] args) {
NumberGenerator generator = new RandomNumberGenerator();
Observer observer1 = new DigitObserver();
Observer observer2 = new GraphObserver();
generator.addObserver(observer1);
generator.addObserver(observer2);
generator.execute();
}
}
18. Memento
ある時点でのインスタンスの状態を記録して保存しておき、あとでインスタンスをその時点の状態に戻す
Caretaker役はOriginator役にお願いして、現在の状態を表現するMemento役を作ってもらう
Caretaker役はMemento役の内部情報は知らないし、気にしない。将来の復元に備えて、大事にMemento役を保存しておく。必要なときに引き出しの奥からMemento役を取り出して、Originator役にわたすと、きちんと復元される
Caretaker役とOriginator役
Caretaker役 : どのタイミングでスナップショットを撮るかを決め、いつUndoするかを決め、Memnto役を保持
Originator役: Memento役を作る仕事と、与えられたMemento役を使って自分の状態を元に戻す仕事
Caretaker役で現在のOriginator役の状態を保存したいときに、そのことをOriginator役に伝える
import game.Memento;
import game.Gamer;
public class Main {
public static void main(String[] args) {
Gamer gamer = new Gamer(100);
Memento memento = gamer.createMemento();
for (int i = 0; i < 100; i++) {
System.out.println("==== " + i);
System.out.println("現状:" + gamer);
gamer.bet();
System.out.println("所持金は" + gamer.getMoney() + "円になりました。");
if (gamer.getMoney() > memento.getMoney()) {
System.out.println(" (だいぶ増えたので、現在の状態を保存しておこう)");
memento = gamer.createMemento();
} else if (gamer.getMoney() < memento.getMoney() / 2) {
System.out.println(" (だいぶ減ったので、以前の状態に復帰しよう)");
gamer.restoreMemento(memento);
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
System.out.println("");
}
}
}
Originator役で、Memento役を作ったり、リストアしたりする。
package game;
import java.util.*;
public class Gamer {
private int money;
private List fruits = new ArrayList();
private Random random = new Random();
private static String[] fruitsname = {
"リンゴ", "ぶどう", "バナナ", "みかん",
};
public Gamer(int money) {
this.money = money;
}
public int getMoney() {
return money;
}
public void bet() {
int dice = random.nextInt(6) + 1;
if (dice == 1) {
money += 100;
System.out.println("所持金が増えました。");
} else if (dice == 2) {
money /= 2;
System.out.println("所持金が半分になりました。");
} else if (dice == 6) {
String f = getFruit();
System.out.println("フルーツ(" + f + ")をもらいました。");
fruits.add(f);
} else {
System.out.println("何も起こりませんでした。");
}
}
public Memento createMemento() {
Memento m = new Memento(money);
Iterator it = fruits.iterator();
while (it.hasNext()) {
String f = (String)it.next();
if (f.startsWith("おいしい")) {
m.addFruit(f);
}
}
return m;
}
public void restoreMemento(Memento memento) {
this.money = memento.money;
this.fruits = memento.getFruits();
}
public String toString() {
return "[money = " + money + ", fruits = " + fruits + "]";
}
private String getFruit() {
String prefix = "";
if (random.nextBoolean()) {
prefix = "おいしい";
}
return prefix + fruitsname[random.nextInt(fruitsname.length)];
}
}
Memento役で、Originator役の内部情報をまとめるクラス
package game;
import java.util.*;
public class Memento {
int money;
ArrayList fruits;
public int getMoney() {
return money;
}
Memento(int money) {
this.money = money;
this.fruits = new ArrayList();
}
void addFruit(String fruit) {
fruits.add(fruit);
}
List getFruits() {
return (List)fruits.clone();
}
}
Memento役
wide interface
オブジェクトの状態をもとに戻すために必要な情報がすべて得られるメソッドの集合
これを使えるのはOriginator役のみ
narrow interface
外部のCaretaker役に見せるもの
内部状態が外部に公開されるのを防ぐ
19. State
状態をクラスで表現。個々の具体的な状態を別々のクラスとして表現して問題を分割
新しい状態を追加するのが簡単
状態遷移を状態に依存した振る舞いとして見なし、個々の具体的な状態のクラスのメソッド中で他のクラスを呼び出すメリット/デメリット
他の状態に遷移するのはいつかという情報が、1つのクラス内にまとまっている
1つのConcreateState役が他のConcreate役を知らなければならない
抽象化したState役のクラス
public interface State {
public abstract void doClock(Context context, int hour);
public abstract void doUse(Context context);
public abstract void doAlarm(Context context);
public abstract void doPhone(Context context);
}
具象化したState役のクラス
public class DayState implements State {
private static DayState singleton = new DayState();
private DayState() {
}
public static State getInstance() {
return singleton;
}
public void doClock(Context context, int hour) {
if (hour < 9 || 17 <= hour) {
context.changeState(NightState.getInstance());
}
}
public void doUse(Context context) {
context.recordLog("金庫使用(昼間)");
}
public void doAlarm(Context context) {
context.callSecurityCenter("非常ベル(昼間)");
}
public void doPhone(Context context) {
context.callSecurityCenter("通常の通話(昼間)");
}
public String toString() {
return "[昼間]";
}
}
抽象化したContext役のクラス。現在の状態を表すConcreteState役を持つ。
public interface Context {
public abstract void setClock(int hour);
public abstract void changeState(State state);
public abstract void callSecurityCenter(String msg);
public abstract void recordLog(String msg);
}
具象化したContext役のクラス
import java.awt.Frame;
import java.awt.Label;
import java.awt.Color;
import java.awt.Button;
import java.awt.TextField;
import java.awt.TextArea;
import java.awt.Panel;
import java.awt.BorderLayout;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
public class SafeFrame extends Frame implements ActionListener, Context {
private TextField textClock = new TextField(60);
private TextArea textScreen = new TextArea(10, 60);
private Button buttonUse = new Button("金庫使用");
private Button buttonAlarm = new Button("非常ベル");
private Button buttonPhone = new Button("通常通話");
private Button buttonExit = new Button("終了");
private State state = DayState.getInstance();
public SafeFrame(String title) {
super(title);
setBackground(Color.lightGray);
setLayout(new BorderLayout());
add(textClock, BorderLayout.NORTH);
textClock.setEditable(false);
add(textScreen, BorderLayout.CENTER);
textScreen.setEditable(false);
Panel panel = new Panel();
panel.add(buttonUse);
panel.add(buttonAlarm);
panel.add(buttonPhone);
panel.add(buttonExit);
add(panel, BorderLayout.SOUTH);
pack();
show();
buttonUse.addActionListener(this);
buttonAlarm.addActionListener(this);
buttonPhone.addActionListener(this);
buttonExit.addActionListener(this);
}
public void actionPerformed(ActionEvent e) {
System.out.println(e.toString());
if (e.getSource() == buttonUse) {
state.doUse(this);
} else if (e.getSource() == buttonAlarm) {
state.doAlarm(this);
} else if (e.getSource() == buttonPhone) {
state.doPhone(this);
} else if (e.getSource() == buttonExit) {
System.exit(0);
} else {
System.out.println("?");
}
}
public void setClock(int hour) {
String clockstring = "現在時刻は";
if (hour < 10) {
clockstring += "0" + hour + ":00";
} else {
clockstring += hour + ":00";
}
System.out.println(clockstring);
textClock.setText(clockstring);
state.doClock(this, hour);
}
public void changeState(State state) {
System.out.println(this.state + "から" + state + "へ状態が変化しました。");
this.state = state;
}
public void callSecurityCenter(String msg) {
textScreen.append("call! " + msg + "\n");
}
public void recordLog(String msg) {
textScreen.append("record ... " + msg + "\n");
}
}
20. Flyweight
インスタンスをできるだけ共有させて、無駄にnewしない
管理されているインスタンスはガベージ対象になれない点について注意
Flyweight役。普通に扱うとプログラムが重くなるので共有した方がよいもの。
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
public class BigChar {
private char charname;
private String fontdata;
public BigChar(char charname) {
this.charname = charname;
try {
BufferedReader reader = new BufferedReader(
new FileReader("big" + charname + ".txt")
);
String line;
StringBuffer buf = new StringBuffer();
while ((line = reader.readLine()) != null) {
buf.append(line);
buf.append("\n");
}
reader.close();
this.fontdata = buf.toString();
} catch (IOException e) {
this.fontdata = charname + "?";
}
}
public void print() {
System.out.print(fontdata);
}
}
FlyweightFactoryの役。Flyweight役を作る工場。
import java.util.HashMap;
public class BigCharFactory {
private HashMap pool = new HashMap();
private static BigCharFactory singleton = new BigCharFactory();
private BigCharFactory() {
}
public static BigCharFactory getInstance() {
return singleton;
}
public synchronized BigChar getBigChar(char charname) {
BigChar bc = (BigChar)pool.get("" + charname);
if (bc == null) {
bc = new BigChar(charname);
pool.put("" + charname, bc);
}
return bc;
}
}
Clientの役。FlyweightFactory役を使ってFlyweight役を作り出し、それを利用する役。
public class BigString {
private BigChar[] bigchars;
public BigString(String string) {
bigchars = new BigChar[string.length()];
BigCharFactory factory = BigCharFactory.getInstance();
for (int i = 0; i < bigchars.length; i++) {
bigchars[i] = factory.getBigChar(string.charAt(i));
}
}
public void print() {
for (int i = 0; i < bigchars.length; i++) {
bigchars[i].print();
}
}
}
21. Proxy
実際に必要な段階になってはじめて、対象のクラスを生成する。
Proxy役とRealSubject役を同一視するためのインターフェースを定義
public interface Printable {
public abstract void setPrinterName(String name);
public abstract String getPrinterName();
public abstract void print(String string);
}
RealSubject役。重い処理の想定。
public class Printer implements Printable {
private String name;
public Printer() {
heavyJob("Printerのインスタンスを生成中");
}
public Printer(String name) {
this.name = name;
heavyJob("Printerのインスタンス(" + name + ")を生成中");
}
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);
} catch (InterruptedException e) {
}
System.out.print(".");
}
System.out.println("完了。");
}
}
Proxy役はClient役からの要求をできるだけ処理。もし、自分だけで処理できず、必要になってからはじめてProxy役はRealSubject役に仕事を任せる。
public class PrinterProxy implements Printable {
private String name;
private Printer real;
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);
}
}
}
Client役。Proxyパターンを利用
public class Main {
public static void main(String[] args) {
Printable p = new PrinterProxy("Alice");
System.out.println("名前は現在" + p.getPrinterName() + "です。");
p.setPrinterName("Bob");
System.out.println("名前は現在" + p.getPrinterName() + "です。");
p.print("Hello, world.");
}
}
22. Command
命令をオブジェクトとして表現することで、履歴を取ったり再実行を行ったりすることができるようになる
抽象化されたCommand役
package command;
public interface Command {
public abstract void execute();
}
具象化されたCommand役
package drawer;
import command.Command;
import java.awt.Point;
public class DrawCommand implements Command {
protected Drawable drawable;
private Point position;
public DrawCommand(Drawable drawable, Point position) {
this.drawable = drawable;
this.position = position;
}
public void execute() {
drawable.draw(position.x, position.y);
}
}
命令の受け取り手となるReceiver役
package drawer;
import command.*;
import java.util.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class DrawCanvas extends Canvas implements Drawable {
private Color color = Color.red;
private int radius = 6;
private MacroCommand history;
public DrawCanvas(int width, int height, MacroCommand history) {
setSize(width, height);
setBackground(Color.white);
this.history = history;
}
public void paint(Graphics g) {
history.execute();
}
public void draw(int x, int y) {
Graphics g = getGraphics();
g.setColor(color);
g.fillOval(x - radius, y - radius, radius * 2, radius * 2);
}
}
上記インターフェイス
package drawer;
public interface Drawable {
public abstract void draw(int x, int y);
}
ConcreteCommand役を生成し、その際にReceiver役を割り当てるClient役
import command.*;
import drawer.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
public class Main extends JFrame implements ActionListener, MouseMotionListener, WindowListener {
private MacroCommand history = new MacroCommand();
private DrawCanvas canvas = new DrawCanvas(400, 400, history);
private JButton clearButton = new JButton("clear");
public Main(String title) {
super(title);
this.addWindowListener(this);
canvas.addMouseMotionListener(this);
clearButton.addActionListener(this);
Box buttonBox = new Box(BoxLayout.X_AXIS);
buttonBox.add(clearButton);
Box mainBox = new Box(BoxLayout.Y_AXIS);
mainBox.add(buttonBox);
mainBox.add(canvas);
getContentPane().add(mainBox);
pack();
show();
}
public void actionPerformed(ActionEvent e) {
if (e.getSource() == clearButton) {
history.clear();
canvas.repaint();
}
}
public void mouseMoved(MouseEvent e) {
}
public void mouseDragged(MouseEvent e) {
Command cmd = new DrawCommand(canvas, e.getPoint());
history.append(cmd);
cmd.execute();
}
public void windowClosing(WindowEvent e) {
System.exit(0);
}
public void windowActivated(WindowEvent e) {}
public void windowClosed(WindowEvent e) {}
public void windowDeactivated(WindowEvent e) {}
public void windowDeiconified(WindowEvent e) {}
public void windowIconified(WindowEvent e) {}
public void windowOpened(WindowEvent e) {}
public static void main(String[] args) {
new Main("Command Pattern Sample");
}
}
命令の実行を開始するInvoker役。Command役で定義されているインターフェースを呼び出す役
Mainクラス(ConcreteCommand役を生成し、その際にReceiver役を割り当てるClient役も兼ねる)
DrawCanvasクラス(命令の受け取り手となるReceiver役も兼ねる)
23. Interpreter
プログラムが解決しようとしている問題を簡単なミニ言語で表現
AbstrtactExpressionの役。構文木のノードに共通のインタフェースを定義する役。
public abstract class Node {
public abstract void parse(Context context) throws ParseException;
}
TerminalExpressionの役。BNFのターミナル・エクスプレッションに対応する役。
public class PrimitiveCommandNode extends Node {
private String name;
public void parse(Context context) throws ParseException {
name = context.currentToken();
context.skipToken(name);
if (!name.equals("go") && !name.equals("right") && !name.equals("left")) {
throw new ParseException(name + " is undefined");
}
}
public String toString() {
return name;
}
}
NonterminalExpressionの役。BNFのノンターミナル・エクスプレッションに対応する役。以下が例。他も例多数。
public class ProgramNode extends Node {
private Node commandListNode;
public void parse(Context context) throws ParseException {
context.skipToken("program");
commandListNode = new CommandListNode();
commandListNode.parse(context);
}
public String toString() {
return "[program " + commandListNode + "]";
}
}
Contextの役。インタプリタが構文解析を行うための情報を提供する役。
import java.util.*;
public class Context {
private StringTokenizer tokenizer;
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;
}
}
Client役。構文木を組み立てるために、TerminalExpression役やNonterminalExpression役を呼び出す役。
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();
}
}
}
マルチスレッド
マルチスレッド
Overview
Single Threaded Execution - この橋を渡れるのは、たった一人
Immutable - 壊したくとも、壊せない
Guarded Suspension - 用意できるまで、待っててね
Balking - 必要なかったら、やめちゃおう
Producer-Consumer - わたしが作り、あなたが使う
Read-Write Lock - みんなで読むのはいいけれど、読んでる間は書いちゃだめ
Thread-Per-Message - この仕事、やっといてね
Worker Thread - 仕事が来るまで待ち、仕事が来たら働く
Future - 引換券を、お先にどうぞ
Two-Phase Termination - 後片付けしてから、おやすみなさい
Thread-Specific Storage - スレッドごとのコインロッカー
Active Object - 非同期メッセージを受け取る、能動的なオブジェクト
詳細
Single Threaded Execution - この橋を渡れるのは、たった一人
synchronizedで同時に1スレッドからしか実行できないようにする
アトミックな操作
基本型・参照型の代入・参照はアトミックな操作
ただし、long,double型の代入・参照はアトミックな操作ではない
long,doubleをスレッド間で共有する場合、synchroizedの中に入れるかvolatileとして宣言
ある領域を一つのスレッドだけに限定していたところをN個までに広げたものを計数セマフォ
Immutable - 壊したくとも、壊せない
set系メソッドのないクラス
以下のような工夫で子のクラスから変更されることを防止
finalとして宣言
フィールドはprivate
Guarded Suspension - 用意できるまで、待っててね
自分の状態が適切な状態のときだけ、目的の処理をスレッドに実行させる
オブジェクトが適切な状態であることをガード条件で表現
呼び方
guarded suspension
guarded wait
条件のテストにwhileを使い、待つためにwaitメソッドを使用。条件が変化したら、notify/notifyAllメソッドを使用
busy wait
スレッドがwaitで待つのではなく、yieldしながら条件をテストするような実装方法
spin lock
条件が成り立つまでwhileループで回って待つ様子を表現
polling
あるイベントが起こるのを繰り返し調べに行き、起こったらそれを調査する方法
Balking - 必要なかったら、やめちゃおう
オブジェクトが適切な状態であることをガード条件で表現
ガード条件が満たされているときに限り、実行を継続。ガード条件が満たされていなければ、処理を実行せずにすぐにメソッドから帰る。
タイムアウト
synchronizedブロック
タイムアウトもinterruptもできない
waitを実行してウェイトウェットの中にある状態
guarded timed
BalkingとGuarded Suspensionの中間でガード条件を満たすまで一定時間待つ
スレッドがオブジェクトのウェイトセットに入って停止した状態から抜け出す
方法
notify : インスタンスに対して実行
notifyAll : インスタンスに対して実行
interrupt : スレッドに対して実行
wait(タイムアウト値)
notify/notifyAllとタイムアウトの場合を識別する方法はない。interruptの場合はInterruptedExceptionの例外が投げられる
詳細
waitの場合でもsleepの場合でもjoinの場合でも同様にinterruptで中断できる
throws InterruptedExceptionがついている
時間はかかるけどしれないがキャンセルできる
notify/notifyAll
java.lang.Objectクラスのメソッドで、そのインスタンスのウェイトセットの中にあるスレッドを起こす
スレッドを直接指定するものではなくインスタンスのロックを撮る必要がある
notifyについてウェイトセットの中にあるスレッドのどれを選択するかは仕様で定められておらず、Javaの処理系に依存
interrupt
インタラプト状態にするメソッド
InterruptedExceptionが指定されているメソッドは時間がかかるかもしれないが処理をキャンセルできる
java.lang.Threadクラスのメソッドでスレッドを直接指定して呼び出すもの
interrupted
インタラプト状態のテストとクリアを行うメソッド
stop
Threadクラスのstopメソッドはdeperecated
安全性を壊す可能性があるため使ってはいけない
スレッドがクリティカルセクションを実行している途中であってもいきなり終了させることができる
java.util.concurrent
例外でタイムアウト
Futureインターフェイスのgetメソッド
Exchangerインターフェイスのexchangeメソッド
CyclicBaririerインターフェイスのawaitメソッド
CountDownLatchインターフェイスのwaitメソッド
戻り値でタイムアウト
BlockingQueue
Semaphore
locks.Locks
Producer-Consumer - わたしが作り、あなたが使う
Data役を作るProcuder役のスレッドから、Data役の中継地点、橋渡し、通信路の働きをするChannel役に渡したいData役を保持させて、Data役を使うConsumer役のスレッドにデータを渡す
Channel役
種類
キュー
スタック
優先度付きキュー
java.util.concurrent
BlockingQueue
ArrayBlockingQueue
ConcurrentLinkedQueue
Exchanger
Read-Write Lock - みんなで読むのはいいけれど、読んでる間は書いちゃだめ
readとwriteという2つの処理を提供するSharedResource役がいる。readはSharedResource役の状態を変更しない処理でwriteは変える処理
read中に他のreadに対するロックを他の組み合わせと同様にロックするとリソースがもったいなく排他制御が不要
そのため、read用のロックとwrite用のロックを別々に提供するReadWriteLock役を導入
Thread-Per-Message - この仕事、やっといてね
Client役はHost役のrequestメソッドを呼び出して要求を出す。その要求を実際に処理するためのHelper役のhandleメソッドがある。このとき要求を処理するために、新しいスレッドをHost役の中で起動して、新しいスレッドがhandleの呼び出しを行うことで、非同期のメッセージ送信を実現
応答性を上げ、遅延時間を下げる
処理の順序が問題にならず、戻り値が不要な場合に使用する
Worker Thread - 仕事が来るまで待ち、仕事が来たら働く
Thread-Per-Messageのようにリクエストのたびに新しいスレッドを起動するのは無駄なので、仕事を実行するスレッドを予め起動しておき、Producer-Consumerを使って、仕事の内容を表すインスタンスをワーカースレッドにわたす
Future - 引換券を、お先にどうぞ
Thread-Per-Messageでは処理を任せた時点では結果を得られなかったが、処理の結果を表すRealData役と同じインターフェイスAPIを持つFuture役を作る
処理の開始時点ではFuture役を戻り値として、別のスレッドの処理が完了したら、その結果をFuture役にセット。Client役はFuture役を使って処理の結果を得る
Two-Phase Termination - 後片付けしてから、おやすみなさい !
概要
動作しているスレッドを終了させ、終了する前には、特定の終了処理を行う場合、オブジェクトの安全性を失わせないために終了要求を表すメソッドを用意
そのメソッドの中で終了要求がきたというフラグを立てる。スレッドは終了処理をはじめても良い安全なポイントでそのフラグをテスト。終了処理への移行はあくまで終了するスレッド自身の判断で行う。
Javaではwait,sleep,joinの各メソッドで待っているスレッドを中断させるため、interruptメソッドを使用。Thread.interruptedメソッドを呼び出すとインタラプト状態がクリアされるので、終了要求のテストには注意
実行時に例外がおきたときでも確実に終了処理を行わせるにはfinallyを使用する
スレッドの終了方法 !!!
非推奨
java.lang.Threadのstopメソッドは、スレッドを強制終了するが安全性が保証されずdeprecated
java.lang.ThreadDeathの例外を投げる
推奨
shutdownRequestメソッドでshutdownRequestedフラグ判定だけを入れつつ、interruptメソッドも使用
理由
shutdownRequestメソッドでshutdownRequestedフラグ判定だけではなくinterruptメソッドを使用しているのは、スレッドがsleepしているかもしれないから
逆にinterrupstメソッドのみでshutdownRequestedフラグ判定を入れないと、一箇所でもInterruptedExceptionの例外をcatchしたところで何も処理しないと、インタラプト状態ではなくなり要求がなかったことになる。フラグは終了要求が出されたことを記録しておくためのもの
public class CountupThread extends Thread {
// ¥«¥¦¥ó¥¿¤ÎÃÍ
private long counter = 0;
// ½ªÎ»Í׵᤬½Ð¤µ¤ì¤¿¤étrue
private volatile boolean shutdownRequested = false;
// ½ªÎ»Í×µá
public void shutdownRequest() {
shutdownRequested = true;
interrupt();
}
// ½ªÎ»Í׵᤬½Ð¤µ¤ì¤¿¤«¤É¤¦¤«¤Î¥Æ¥¹¥È
public boolean isShutdownRequested() {
return shutdownRequested;
}
// ưºî
public final void run() {
try {
while (!isShutdownRequested()) {
doWork();
}
} catch (InterruptedException e) {
} finally {
doShutdown();
}
}
// ºî¶È
private void doWork() throws InterruptedException {
counter++;
System.out.println("doWork: counter = " + counter);
Thread.sleep(500);
}
// ½ªÎ»½èÍý
private void doShutdown() {
System.out.println("doShutdown: counter = " + counter);
}
}
public class Main {
public static void main(String[] args) {
System.out.println("main: BEGIN");
try {
// スレッドの起動
CountupThread t = new CountupThread();
t.start();
// 少し時間をあける
Thread.sleep(10000);
// スレッドの終了要求
System.out.println("main: shutdownRequest");
t.shutdownRequest();
System.out.println("main: join");
// スレッドの終了を待つ
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("main: END");
}
}
Thread-Specific Storage - スレッドごとのコインロッカー
対象となるオブジェクトをTSObject役とし、TSObject役と同じインタフェース(API)を持つTSObjectProxy役を作る。Client役→TSObject役の対応表を管理するためにTSObjectCollection役を用意。TSObjectProxy役はTSObjectCollection役を使って、現在のスレッドに対応するTSObjectを取得。そして、そのTSOBject役に処理を委譲
joinメソッドとisAliveメソッド
指定したスレッドの終了を待つにはjava.lang.Threadのjoinメソッド
指定したスレッドが現在終了しているかどうかはjava.lang.ThreadのisAliveメソッド
指定したスレッドの状態を取得するにはjava.lang.ThreadのgetStateメソッド
java.util.concurrent.ExecutorService
shutdown : 動作しているスレッドをきちんと終了
isShutdownメソッド : shutdownメソッドが呼ばれたか調べる
isTerminatedメソッド : スレッドが実際に止まっているかどうか調べるメソッド
Active Object - 非同期メッセージを受け取る、能動的なオブジェクト
処理を依頼する人(Client役)と、処理を実行する人(Servant役)がいる状況について
Servant役が処理を実行するのに時間がかかったり、処理を実行できるタイミングが遅くなったりしてもClient役に影響を与えたくない場合
Client役からServant役への一方向の呼び出しだけではなく、実行結果をServant役からClient役へ返すことができるように双方向の呼び出しにしたい場合
処理の依頼順序と処理の実行順序を独立にしたい場合
処理
Client役からの依頼はProxy役へのメソッド呼び出しとして実現。Proxy役はその依頼をConcreteMethodRequest役という1つのオブジェクトに変換し、Scheduler役を経由して、ActivationQueue役に保存
ActivationQueue役から次に実行すべきConcreteMethodRequest役を選び出して実行するのは、Scheduler役の仕事。Scheduler役はClient役とは別のスレッドで動作し、ConcreteMethodRequest役経由で、Servant役に処理を委譲
volatile
同期化 : あるスレッドがvolatileフィールドに行った書き込みは他のスレッドからすぐに見える
longとdoubleのアトミックな扱い
複数のスレッドで共有されるフィールドは、synchronizedまたはvolatileで守る
Last updated