Javaデザインパターン入門
第2回
今日の講義
1.
2.
3.
4.

Factory Method
Singleton
Abstarct Factory
State
インスタンス生成をサブクラスに任せる
Factory Methodパターン
• 目的
▫ オブジェクトを生成するときのインターフェース
だけを規定して、実際にどのクラスをインスタン
ス化するかはサブクラスが決めるようにする

• 具体的な施策
▫ インスタンス化はすべてサブクラス側で行う
Factory Methodサンプル
• 身分証明書(IDカード)を作る工場
• ProductクラスとFactoryクラスは、framework
というパッケージに属し、この2つのクラスがイ
ンスタンス生成のための枠組み(フレームワー
ク)の役割を果たす
• IDCardクラスとIDCardFactoryクラスは、実際
の肉付けを行う
• Mainクラスは動作テストのためのクラス
サンプル クラス一覧
パッケージ

クラス名

解説

framework

Product

抽象メソッドuseのみ定義されている抽象
クラス

framework

Factory

メソッドcreateを実装している抽象クラス

idcard

IDCard

メソッドuseを実装しているクラス

idcard

IDCardFactory

メソッドcreateProduct,registerProductを
実装しているクラス

無名

Main

動作テスト用のクラス
サンプル クラス図
Product.java
package framwork;
public abstract class Product {
public abstract void use();
}
Factory.java
package framwork;
public abstract class Factory {
public final Product create(String owner) {
Product product = createProduct(owner);
registerProduct(product);
return product;
}
protected abstract Product createProduct(String owner);

protected abstract void registerProduct(Product product);
}
IDCard.java
package idcard;
import framwork.Product;
public class IDCard extends Product {
private String owner;
public IDCard(String owner) {
System.out.println(owner + "のカードを作ります");
this.owner = owner;
}
@Override
public void use() {
System.out.println(owner + "のカードを使います");
}
public String getOwner() {
return this.owner;
}
}
IDCardFactory.java
package idcard;
import java.util.ArrayList;
import java.util.List;
import framwork.Factory;
import framwork.Product;
public class IDCardFactory extends Factory {

private List owners = new ArrayList();
public List getOwners() {
return owners;
}
@Override
protected Product createProduct(String owner) {
return new IDCard(owner);
}
@Override
protected void registerProduct(Product product) {
owners.add(((IDCard)product).getOwner());
}
}
Main.java
import framwork.Factory;
import framwork.Product;
import idcard.IDCardFactory;
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();
}
}
サンプルプログラム補足
• インスタンス生成-メソッドの実装方法
▫ サンプルプログラムでは、Factoryクラスの
createProductメソッドは抽象メソッドとなってい
る。つまり、このメソッドはサブクラスで実装す
ることを期待されている。
▫ createProductメソッドの記述方法は3通りある
1. 抽象メソッドにする
2. デフォルトの実装を用意しておく
3. エラーにする
関連しているパターン
• Template Methodパターン

▫ Factory Methodパターンは、Template Methodパターンの
典型的な応用。サンプルプログラムのcreateメソッドが
Template Methodになっている

• Singletonパターン

▫ Creater役を務めるクラスは、多くの場合、Singletonパ
ターンとして作ることが出来る。

• Compositeパターン

▫ Product役にCompositeパターンを当てはめることが出来る
場合がある

• Iteratorパターン

▫ IteratorパターンでiteratorメソッドがIteratorのインスタン
スを生成するときにFactory Methodパターンが使われるこ
とがある
演習1
• サンプルプログラムのIDCardクラスにカードの
通し番号をつけ、IDCardFactoryクラスが通し
番号と所持者の対応表を持つように修正しなさ
い
たった1つのインスタンス
Singletonパターン
• 目的
▫ 指定したクラスのインスタンスが絶対に1個しか
存在しないことを保証したい
▫ インスタンスが1個しか存在しないことをプログ
ラム上で表現したい
サンプル クラス一覧
クラス名

解説

Singleton

インスタンスが1つしか存在しないクラス

Main

動作テスト用のクラス
サンプル クラス図
Singleton.java
public class Singleton {
private static Singleton singleton = new
Singleton();
private Singleton() {
System.out.println("インスタンスを生成
しました");
}
public static Singleton getInstance() {
return singleton;
}
}
Main.java
public class Main {

です");

public static void main(String[] args) {
System.out.println("Start.");
Singleton obj1 = Singleton.getInstance();
Singleton obj2 = Singleton.getInstance();
if (obj1 == obj2) {
System.out.println("obj1とobj2は同じインスタンス
} else {
System.out.println("obj1とobj2は同じインスタンス

ではありません");
}
System.out.println("End.");
}
}
関連しているパターン
•
•
•
•

AbstractFactoryパターン
Builderパターン
Facadeパターン
Prototypeパターン
関連する部品を組み合わせて製品を作る
Abstract Factoryパターン
• 目的
▫ オブジェクト指向における「抽象的」という言葉
は、「具体的にどのように実装されているかにつ
いては考えず、インターフェース(API)だけに注
目している」状態を指す
▫ 部品の具体的な実装だけには注目せず、インター
フェース(API)に注目し、そのインターフェース
(API)だけを使って、部品を製品にまとめる
Abstract Factoryサンプル
• 階層構造を持ったリンク集をHTMLファイルと
して作る
• factoryパッケージは、抽象的な構造・部品・製
品を含むパッケージ
• 無名パッケージは、Mainクラスを含むパッケー
ジ
• listfactoryパッケージは具体的な構造・部品・製
品を含むパッケージ
サンプル クラス一覧
パッケージ

クラス名

解説

factory

Factory

抽象的な工場を表すクラス(Link,Tray,Pageを作
る)

factory

Item

LinkとTrayを統一的に扱うためのクラス

factory

Link

抽象的な部品:HTMLのリンクを表すクラス

factory

Tray

抽象的な部品:LinkやTrayを集めたクラス

factory

Page

抽象的な部品:HTMLのページを表すクラス

無名

Main

動作テスト用のクラス
サンプル クラス一覧
パッケージ

クラス名

解説

listfactory

ListFactory

具体的な工場を表すクラス
(ListLink,ListTray,Listpageを作る)

listfactory

ListLink

具体的な部品:HTMLのリンクを表すクラス

listfactory

ListTray

具体的な部品:LinkやTrayを集めたクラス

listfactory

ListPage

具体的な製品:HTMLのページを表すクラス
サンプル クラス図
Item.java
package factory;
public abstract class Item {
protected String caption;
public Item(String caption) {
this.caption = caption;
}
public abstract String makeHTML();
}
Link.java
package factory;
public abstract class Link extends Item {
protected String url;
public Link(String caption, String url) {
super(caption);
this.url = url;
}
}
Tray.java
package factory;
import java.util.ArrayList;
public abstract class Tray extends Item {
protected ArrayList<Item> tray = new
ArrayList<Item>();
public Tray(String caption) {
super(caption);
}
public void add(Item item) {
tray.add(item);
}
}
Page.java
package factory;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;

public abstract class Page {
protected String title;
protected String author;
protected ArrayList<Item> content = new ArrayList<Item>();
public Page(String title, String author) {
this.title = title;
this.author = author;
}
public void add(Item item) {
content.add(item);
}
Page.java
public void output() {
try {
String filename = title + ".html";
Writer writer = new FileWriter(filename);
writer.write(this.makeHTML());
writer.close();
System.out.println(filename + "を作成し
ました");
} catch (IOException e) {
e.printStackTrace();
}
}
public abstract String makeHTML();
}
Factory.java
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.out.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);
}
Main.java
package abstractfactory;
import factory.Factory;
import factory.Link;
import factory.Page;
import factory.Tray;
public class Main {
public static void main(String[] args) {
// Linkfactory
Factory factory = Factory.getFactory("listfactory.ListFactory");
Link asahi = factory.createLink("朝日新聞", "http://www.asahi.com/");
Link yomiuri = factory.createLink("読売新聞", "http://www.yomiuri.co.jp/");
Link us_yahoo = factory.createLink("Yahoo!", "http://www.yahoo.com/");
Link jp_yahoo = factory.createLink("Yahoo!Japan", "http://yahoo.co.jp/");
Link excite = factory.createLink("Excite", "http://www.excite.com/");
Link google = factory.createLink("Google", "http://www.google.com/");
Main.java
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", "yukiko kato");
page.add(traynews);
page.add(traysearch);
page.output();
}
}
ListFactory.java
package listfactory;
import factory.Factory;
import factory.Link;
import factory.Page;
import factory.Tray;
public class ListFactory extends Factory {

@Override
public Link createLink(String caption, String url) {
return new ListLink(caption, url);
}
@Override
public Tray createTray(String caption) {
return new ListTray(caption);
}
@Override
public Page createPage(String title, String author) {
return new ListPage(title, author);
}
}
ListLink.java
package listfactory;
import factory.Link;
public class ListLink extends Link {
public ListLink(String caption, String url) {
super(caption, url);
}
@Override
public String makeHTML() {
return " <li><a href="" + url + "">" + caption + "</a></li>n";
}
}
ListTray.java
package listfactory;
import java.util.Iterator;
import factory.Item;
import factory.Tray;
public class ListTray extends Tray {
public ListTray (String caption) {
super(caption);
}
@Override
public String makeHTML() {
StringBuffer sb = new StringBuffer();
sb.append("<li>n");
sb.append(caption + "n");
sb.append("<ul>n");
Iterator<Item> it = tray.iterator();
while(it.hasNext()) {
Item item = (Item) it.next();
sb.append(item.makeHTML());
}
sb.append("</ul>n");
sb.append("</li>n");
return sb.toString();
}
}
ListPage.java
package listfactory;
import java.util.Iterator;
import factory.Item;
import factory.Page;
public class ListPage extends Page{
public ListPage(String title, String author) {
super(title, author);
}
public String makeHTML() {
StringBuffer sb = new StringBuffer();
sb.append("<html><head><title>" + title + "</title></head>n");
sb.append("<body>n");
sb.append("<h1>" + title + "</h1>n");
sb.append("<ul>n");
Iterator<Item> iterator = content.iterator();
while (iterator.hasNext()) {
Item item = iterator.next();
sb.append(item.makeHTML());
}
sb.append("</ul>n");
sb.append("<hr><address>" + author + "</address>");
sb.append("</body></html>n");
return sb.toString();
}
}
関連しているパターン
• Builderパターン

▫ AbstractFactoryパターンは、インターフェース(API)が定まって
いる抽象的な部品を組み合わせて複雑な構造を持ったインスタン
スを作る。
▫ Builderパターンは、段階を追って大きなインスタンスを作る

• FactoryMethodパターン

▫ AbstractFactoryパターンで製品や部品を作るところは、
FactoryMethodパターンになることがある

• Compositeパターン

▫ AbstractFactoryパターンで作られる製品は、Compositeパターン
になることがある

• Singletonパターン

▫ AbstractFactoryパターンの具体的な工場は、Singletonになるこ
とがある
演習2
• サンプルプログラムに具体的な工場
TableFactoryを追加し、tableタグを使用した表
組みのデザインになるようにしなさい。
• なお、tableタグの構成は以下である。
<table>
<tr>
<td>カラム</td>
</tr>
</table>
演習2 表示例
状態をクラスとして表現する
Stateパターン
• 目的
▫ 「状態」というものをクラスとして表現する
▫ 状態をクラスとして表現していれば、クラスを切
り替えることによって「状態の変化」が表わせる
ことができ、新しい状態を追加しなければならな
い時に、何をプログラムすればよいかはっきりす
る
サンプルプログラム
• 金庫警備システム
▫ 時刻ごとに警備の状態が変化する金庫警備システ
ム
▫ 警備センターへの呼び出しは、画面に呼び出し状
況を表示することで対応
▫ 時間経過は、プログラム上の1秒を1時間と仮定す
る
金庫警備システム要件
•
•
•
•
•
•
•
•
•
•
•
•
•

金庫が1つある
金庫は警備センサーと接続されている
金庫には非常ベルと通常通話用の電話が接続されている
金庫には時計がついていて、現在の時刻を監視している
昼間は9:00〜16:59、夜間は17:00〜23:59及び0:00〜8:59の範囲
金庫は昼間だけ使用できる
昼間、金庫を使用すると、警備センターに使用記録が残る
夜間、金庫を使用すると、警備センターに非常事態の通報が行く
非常ベルはいつでも使用できる
非常ベルを使用すると、警備センターに非常ベルの通報が行く
通常通話の電話はいつでも使用できるが、夜間は録音のみとする
昼間、電話を使用すると、警備センサーが呼び出される
夜間、電話を使用すると、警備センサーの留守録が呼び出される
Stateパターンを使用しない金庫警備シ
ステムの擬似コード
警備システムのクラス {
金庫使用時に呼ばれるメソッド() {
if (昼間) {
警備センターに利用の記録
} else if (夜間) {
警備センターに非常事態の通報
}
}
非常ベル使用時に呼ばれるメソッド() {
警備センターに非常ベルの通報
}
通常通話時に呼ばれるメソッド() {
if (昼間) {
警備センターの呼び出し
} else if (夜間) {
警備センターの留守録の呼び出し
}
}
}
Stateパターンを使用した金庫警備シス
テムの擬似コード
昼間という状態を表現するクラス {
金庫使用時に呼ばれるメソッド() {
警備センターに利用の記録
}
非常ベル使用時に呼ばれるメソッド() {
警備センターに非常ベルの通報
}
通常通話時に呼ばれるメソッド() {
警備センターの呼び出し
}
}
夜間という状態を表現するクラス {
金庫使用時に呼ばれるメソッド() {
警備センターに非常事態の通報
}
非常ベル使用時に呼ばれるメソッド() {
警備センターに非常ベルの通報
}
通常通話時に呼ばれるメソッド() {
警備センターの留守録の呼び出し
}
}
パターンを適用した時の差異
• Stateパターンを使用しないコードではif文を使
用するが、Stateパターンを使うことにより、昼
間・夜間という状態がクラスとして表現される
ため、状態チェックのためのif文は使用しない
サンプル クラス一覧
クラス名

解説

State

金庫の状態を表すインターフェース

DayState

Stateを実装しているクラス。昼間の状態を表す

NightState

Stateを実装しているクラス。夜間の状態を表す

Context

金庫の状態変化を管理し、警備センターと連絡を取るインター
フェース

SafeFrame

Contextを実装しているクラス。ボタンや画面表示などのユー
ザインターフェースを持つ

Main

動作テスト用のクラス
サンプル クラス図
State.java(インターフェース)
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);
}
DayState.java
public class DayState implements State {
private static DayState singleton = new DayState();
private DayState() {
}

public static State getInstance() {
return singleton;
}
@Override
public void doClock(Context context, int hour) {
if (hour < 9 || 17 <= hour) {
context.changeState(NightState.getInstance());
}
}
DayState.java
@Override
public void doUse(Context context) {
context.recordLog("金庫使用(昼間)");
}
@Override
public void doAlarm(Context context) {
context.callSecurityCenter("非常ベル(昼間)");
}
@Override
public void doPhone(Context context) {
context.callSecurityCenter("通常の通話(昼間)");
}
@Override
public String toString() {
return "[昼間]";
}
}
NightState.java
public class NightState implements State {
private static NightState singleton = new NightState();
private NightState() {
}
public static State getInstance() {
return singleton;
}
@Override
public void doClock(Context context, int hour) {
if (9 <= hour && hour < 17) {
context.changeState(DayState.getInstance());
}
}
NightState.java
@Override
public void doUse(Context context) {
context.callSecurityCenter("非常:夜間の金庫使用!");
}
@Override
public void doAlarm(Context context) {
context.callSecurityCenter("非常ベル(夜間)");
}
@Override
public void doPhone(Context context) {
context.recordLog("夜間の通話録音");
}
@Override
public String toString() {
return "[夜間]";
}
}
Context.java(インターフェース)
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);
}
SafeFrame.java
import java.awt.BorderLayout;
import java.awt.Button;
import java.awt.Color;
import java.awt.Frame;
import java.awt.Panel;
import java.awt.TextArea;
import java.awt.TextField;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class SafeFrame extends Frame implements Context , ActionListener{
// 現在時刻表示
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();
SafeFrame.java
public SafeFrame(String title) {
super(title);
setBackground(Color.lightGray);
setLayout(new BorderLayout());
// textClockを配置
add(textClock, BorderLayout.NORTH);
textClock.setEditable(false);
// textScreenを配置
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();
setVisible(true);

// リスナーの設定
buttonUse.addActionListener(this);
buttonAlarm.addActionListener(this);
buttonPhone.addActionListener(this);
buttonExit.addActionListener(this);
}
SafeFrame.java
// ボタンが押されたらここに来る
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("?");
}
}
SafeFrame.java
// 時刻の設定
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);
}
SafeFrame.java
// 状態変化
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");
}
}
Main.java
public class Main {
public static void main(String[] args) {
SafeFrame frame = new SafeFrame("State Sample");
while (true) {
for (int hour = 0; hour < 24; hour++) {
frame.setClock(hour); // 時刻の設定
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
}
}
}
}
Stateパターンの登場人物
• State(状態)の役

▫ 状態を表す
▫ 状態ごとに異なる振る舞いをするインターフェース
(API)を定める
▫ このインターフェース(API)は、状態に依存した振る
舞いをするメソッドの集まりになる

• ConcreteState(具体的な状態)の役

▫ 具体的な個々の状態を表現する
▫ State役で定められたインターフェースを実装する

• Context(状況、前後関係、文脈)の役

▫ 現在の状態を表すConcreteState役を持つ
▫ Stateパターンの利用者に必要なインターフェース
(API)を定める
関連しているパターン
• Singletonパターン
▫ ConcreteState役はSingletonパターンとして実装
される場合がある
 状態を表すクラスは、インスタンスフィールド(つ
まりインスタンスの状態)を持っていないため

• Flyweightパターン
▫ 状態を表すクラスはインスタンスフィールドを持
たないため、Flyweightパターンを使って、
ConcreteState役を複数のContext役で共有出来る
場合がある
演習3
• サンプルプログラムに、「昼食時」(12:00~
12:59)という以下のような新しい状態を追加し
なさい
▫ 昼食時、金庫を使用すると、警備センターに非常
事態の通報が行く
▫ 昼食時、非常ベルを使用すると、警備センターに
非常ベルの通報が行く
▫ 昼食時、電話を使用すると、警備センターの留守
録が呼び出される
参考
• [増補改訂版]Java言語で学ぶデザインパターン
入門
▫ 結城浩著

Javaデザインパターン入門【第2回】