19_Observerパターン

●目的

あるオブジェクトの状態が変化したことをチェックしたい。

●よくある問題点

あるオブジェクト1があるとして、そのオブジェクト1の状態が変化したときに、別のオブジェクト2でそのオブジェクト1の状態の変化に合わせて、何かをやりたいというケースがあると思います。
オブジェクト1は自分の状態変化によってオブジェクト2が何をやりたいのかは知らないので、オブジェクト1と2の関係は連携とは言いにくい一方的なものとなります。この代表的な例がマウスイベントの通知になると思います。

このようにオブジェクトに通知機能をもたせたり、また前述したようなオブジェクト1と2が「1:n」や「n:1」になったりする場合(オブジェクト2が通知するオブジェクト1を複数持つ構成になったり、オブジェクト1の通知相手が複数になる構成になったりする場合)にも対処できるようにするためには、どのようにしたらよいのかを次の「解決方法」でみていきたいと思います。

●解決方法

前述した例で考えると、オブジェクト1は状態の変化を観察される側になり、オブジェクト2はオブジェクト1の状態変化を観察する側(=オブザーバー)になります。

まず、監視するオブジェクトが実装するためのインターフェース「InterfaceObserver」を用意します。今回はこのインタフェースを実装するクラスObserverAとObserverBを用意します。

また、監視される側のクラスは「MyClass」とします。MyClassは複数のオブザーバーが登録できるように内部にVectorオブジェクトを用意し、addObserverメソッドを用意します。(※直感的に複数の値を次々と追加できることがわかるよう、setではなくadd〜というメソッドのネーミングにしています。)

MyClassは、このVectorに登録されているオブジェクトの数だけ、for文のなかでオブザーバーとなっているオブジェクトに通知を行っています。

以上が、Observerパターンになります。

●図解

●サンプルコード

・InterfaceObserver.java(インターフェース)

public interface InterfaceObserver{
void done (int updateValue);
}

・ObserverA.java(インタフェース”InterfaceObserver”を実装)

public class ObserverA implements InterfaceObserver {

@Override
public void done(int updateValue) {
System.out.println(“MyObserverA (Updated : ” + updateValue + “)”);
}

}

・ObserverB.java(インタフェース”InterfaceObserver”を実装)

public class ObserverB implements InterfaceObserver {

@Override
public void done(int updateValue) {
System.out.println(“MyObserverB (Updated : ” + updateValue + “)”);
}

}

・MyClass.java(状態変化を通知するクラス)

import java.util.Vector;

class MyClass {
int value = 0;

Vector observers = new Vector();

void addObserver(InterfaceObserver observer){
observers.add(observer);
}

void doIt(){
value++;

//オブザーバーの全オブジェクトに通知
for(int i = 0; i < observers.size(); i++){
InterfaceObserver observer = (InterfaceObserver)observers.get(i);
observer.done(value);
}
}
}

・Main.java(クライアント側プログラム)

class Main {

public static void main(String[] args) {
MyClass myclass = new MyClass();
//監視する側のオブジェクトを生成
ObserverA observerA = new ObserverA();
ObserverB observerB = new ObserverB();

//myclassにオブザーバーを登録
myclass.addObserver(observerA);
myclass.addObserver(observerB);

//myclassで状態変化を3回実行させてみる(MyClassのvalueに++)
myclass.doIt();
myclass.doIt();
myclass.doIt();

}

}

●注意

Observerパターンには、注意点があります。
それは、状態変化を監視するオブザーバーのオブジェクト間に関連がある場合です。なぜこれが問題になるかというと、Observerパターンではオブザーバーに通知する順序までは明確にされていないためです。どのオブザーバーが先に通知を受け取るかによって、動作が変わってしまうことに注意しなければなりません。

また、監視対象となっているオブジェクトにも強制的に状態変化を発生させるようなこともやらない方がよいと思います。もし、そのオブジェクトに他にオブザーバーがいるとそのオブジェクトに影響が出てしまいます。

03_Builderパターン

●目的

複雑な手順でオブジェクトを生成する。

●よくある問題点

オブジェクトを生成する際、典型的にはそのクラスのコンストラクタを呼び出す。
ところが、コンストラクタをつかってオブジェクトを生成する場合、1度きりのコンストラクタの呼び出しで、オブジェクトの生成を済ませなければならないという、小さな欠点がある。

以下の例を見てほしいのですが、3種類のパラメータがあったとすると、それ毎にコンストラクタの種類が増える場合があり、選択も重荷になってしまいます。

class MyClass{
MyClass(int pram1){・・・}
MyClass(String pram2){・・・}
MyClass(double pram3){・・・}
MyClass(int pram1, String pram2){・・・}
MyClass(int pram1, double pram3){・・・}
MyClass(String pram2, double pram3){・・・}

}

●解決方法

正しい生成手順を予め別の専用クラスにまとめる形が効果的です.
これを「Builderパターン」と呼びます.そして、コンストラクタを分割したメソッドを持つクラスを「ビルダ(Builder)クラス」と呼び、ビルダクラスのメソッドを生成手順に沿って呼び出すクラスのことを「ディレクタ(Director)クラス」と呼びます。もし、呼び出しの手順が変更となった場合は生成手順に従って呼び出しているDirectorクラスを変更できます。

●図解

●サンプルコード

■MyClass.java

class MyClass {

private String name;

MyClass(String name){
this.name = name;
}

}

■Builder.java

class Builder {

String name;

void addFirstName(){
name += “Tarou”;
}

void addMiddleName(){
name += “Steve”;
}

void addLastName(){
name += “Sato”;
}

MyClass getMyClass(){
MyClass result = new MyClass(name);
name = “”;
return result;
}

}

■Director.java

class Director {
MyClass createMyClass(Builder builder){
builder.addFirstName();
builder.addMiddleName();
builder.addLastName();

return builder.getMyClass();
}
}

■Main.java

class Main {
Builder builder = new Builder();

Director director = new Director();
MyClass myClass = director.createMyClass(builder);

}

02_Abstract Factoryパターン

●目的

組み合わせで使用する多数のサブクラス群を、まとめて交換する用途に便利。

●よくある問題点

・データベースに問合せにいく処理をごっそり変更したい。
・プラットフォーム対応で、ソースの一部分を変更する必要がある。
・以上のことをFactory Methodパターンで実現しようとしたが、一見何にも関係なさそうなサブクラスに対するファクトリメソッドたちも一カ所に集めなければならず、Factory Methodパターンでは不自然な構成になり、無理が出てきた。

●解決方法

Factory Methodパターンを進化させたような発想で、サブクラスのオブジェクトの生成を専門に引き受けるクラス(ファクトリクラス)を1つ用意し、各サブクラスごとのファクトリメソッドをそのクラスにまとめて持たせることを考える。(図解参照)

●図解

●サンプルコード

■AbstractFactory.java
abstract class AbstractFactory {
abstract AbstractClassA createClassA();
abstract AbstractClassB createClassB();

//PCの所有者をチェックして、必要なファクトリオブジェクトを返す。
static AbstractFactory getFactory(String owner){
if(owner.equals(“Sato”)){
return new Factory1();
}else if(owner.equals(“Nakamura”)){
return new Factory2();
}else{
return null;
}
}
}

■Factory1.java
class Factory1 extends AbstractFactory {

@Override
AbstractClassA createClassA() {return new ClassA1();}

@Override
AbstractClassB createClassB() {return new ClassB1();}

}

■Factory2.java
class Factory2 extends AbstractFactory {

@Override
AbstractClassA createClassA() {return new ClassA2();}

@Override
AbstractClassB createClassB() {return new ClassB2();}

}

■AbstractClassA.java
abstract class AbstractClassA {
abstract String getName();
abstract int getAge();

abstract AbstractClassB createClassB();
}

■ClassA1.java
class ClassA1 extends AbstractClassA {

@Override
String getName() {return “Sato”;}

@Override
int getAge() {return 15;}

@Override
AbstractClassB createClassB() {return new ClassB1();}
}

■ClassA2.java
class ClassA2 extends AbstractClassA {

@Override
String getName() {return “Nakamura”;}

@Override
int getAge() {return 30;}

@Override
AbstractClassB createClassB() {return new ClassB1();}
}

■AbstractClassB.java
abstract class AbstractClassB {
abstract String getVendor();
abstract String getDate();
}

■ClassB1.java
class ClassB1 extends AbstractClassB {

@Override
String getVendor() {return “Apple”;}

@Override
String getDate() {return “2000/12/24”;}

}

■ClassB2.java
class ClassB2 extends AbstractClassB {

@Override
String getVendor() {return “IBM”;}

@Override
String getDate() {return “2000/03/03”;}

}

■Main.java
public class Main {

public static void main(String[] args) {
//引数(args)を渡して、適切なオブジェクトを入手する。
AbstractFactory factory = AbstractFactory.getFactory(args[0]);

AbstractClassA classA = factory.createClassA();
AbstractClassB classB = factory.createClassB();

//入手したのがClassA1なのかClassA2なのか知らないまま使う。
System.out.println(classA.getName());
System.out.println(classA.getAge());

//入手したのがClassB1なのかClassB2なのか知らないまま使う。
System.out.println(classB.getVendor());
System.out.println(classB.getDate());

}
}

●注意点

Factory Methodパターンは、あるサブクラスとあるサブクラスをペアにして使うことがはっきり決定されている場合にのみ使用する。それぞれのサブクラスを単独でも使う可能性がある場合は、使用しないこと。

01_Factory Methodパターン

●目的

2つ以上のサブクラスを必ずペア(一緒)にして使ってもらいたい場合

●よくある問題点

以下の2つのクラスがあったとする。
(例)
クラスA:人に関する情報(名前、年齢)
クラスB:場所に関する情報(住所、階数)

クラスAとクラスBは、とある人の情報としてひとまとまりにできる。
しかし、このクラスAとクラスBは1つのクラスにしてしまうと、引っ越しなどで住所が変わってしまった場合には対応できない。かと言って別々のクラスだと、クラスAとBのペア間違いがおこる可能性もある。(名前と住所があべこべになる)

●解決方法

ペアになるオブジェクトを生成してくれるメソッドをクラス1にもつ。これがFactory Methodパターン

●図解

●サンプルコード

■AbstractClassA.java
abstract class AbstractClassA {
abstract String getName();
abstract int getAge();

abstract AbstractClassB createClassB();
}

■AbstractClassB.java
abstract class AbstractClassB {
abstract String getAddress();
abstract int getFloor();
}

■ClassA.java
class ClassA extends AbstractClassA {

@Override
String getName() {return “Sato”;}

@Override
int getAge() {return 15;}

@Override
AbstClassB createClassB() {return new ClassB();}
}

■ClassB.java
class ClassB extends AbstractClassB {

@Override
String getAddress() {return “Tokyo”;}

@Override
int getFloor() {return 26;}

}

■Main.java
public class Main {

public static void main(String[] args) {
//ClassAを生成
ClassA classA = new ClassA();

//ファクトリメソッドを呼び出す
AbstractClassB classB = classA.createClassB();

//どのClassBかを意識しないまま、使用する
System.out.println(classA.getName());
System.out.println(classA.getAge());
System.out.println(classB.getAddress());
System.out.println(classB.getFloor());

}

}

●注意点

Factory Methodパターンは、あるサブクラスとあるサブクラスをペアにして使うことがはっきり決定されている場合にのみ使用する。それぞれのサブクラスを単独でも使う可能性がある場合は、使用しないこと。

GoFのデザインパターン

オブジェクト指向を見直そうと思い、昔勉強したGoFのデザインパターンをブログに載せることにしました。
GoFのデザインパターンは23個あります。このデザインパターンは「The Gang of Four」= GoF(ゴフと呼ぶ)と呼ばれる4人の研究者が定義したものです。

これから、この23個のデザインパターンを見直して、オブジェクト指向について理解を深めたいと思います。