2010年5月16日日曜日

Prototypeパターン

Prototypeパターンとは、インスタンスをnewで生成するのではなく、既にあるインスタンスからコピーをして新しいインスタンスを作るパターンです。このパターンを使う場面としては、似たようなクラスを大量に作りたくない場合や、インスタンス生成が難しい場合(*1)、F/W(*2)とインスタンス生成を分けたい場合などです。

コード例としては、複数のパーツを用意してそれを利用するという形にしています。パーツをコピーできるようにするためのPartインターフェースと、それを利用するManagerクラスをF/Wで用意します。パーツの雛形(まさしく、プロトタイプ)はPartインターフェースを実装すればよいです。実際にサンプルコードを動かしたいときは、PartインターフェースとManagerクラスのソースをframeworkフォルダに入れ、それ以外はframeworkフォルダの外においてください。

Partインターフェースは単純に以下のような感じです。Partはコピーできるので、copy()を用意しています。copy()を実際に実装するときはjava.lang.Objectのclone()を呼ぶので、cloneableインターフェースを継承しています。

package framework;
import java.lang.Cloneable;

public interface Part extends Cloneable {
    void doSomething();
    Part copy();
}

ManagerクラスはPartインターフェースを介してインスタンスのコピーを行うクラスです。register()はPartの登録、copyPart()はPartをコピーして、それを取得します。

package framework;
import java.util.Map;
import java.util.HashMap;

public class Manager {
    private Map<String, Part> showcase = new HashMap<String, Part>();
    public void register(String name, Part part) {
        showcase.put(name, part);
    }
    public Part copyPart(String name) {
        Part p = (Part)showcase.get(name);
        return p.copy();
    }
}


Partインターフェースを実装するクラスでは、doSomething()はそのパーツにあった処理(パーツの名前を決めたり、読み込むファイルを指定したり、フラグを立てたり・・・なんでも構わない)を定義すればよく、copy()はただ単純にjava.lang.Objectのclone()を呼べばいいです。(*3) 

Main(PartとManagerを利用するクラス)では以下のような感じです。最初はもちろんPartインスタンスを生成して登録しておく必要がありますが、その後はManagerさんにcopyPart()をお願いして、コピーを頂くだけです。
import framework.Part;
import framework.Manager;

public class Main {
    public static void main(String[] args) {
        // 準備
        Manager manager = new Manager();
        SamplePart part1 = new SamplePart("part1");
        SamplePart2 part2 = new SamplePart2("part2");
        manager.register("abc", part1);
        manager.register("def", part2);

        // 生成
        Part p1 = manager.copyPart("abc");
        p1.doSomething();
        Part p2 = manager.copyPart("def");
        p2.doSomething();
    }
}


register()に入れる名前は分かりやすいものがいいでしょう。コピーをお願いするときはその名前を指定します。例が今ひとつ分かりづらいですが、雛形としてのPartはManagerに登録されているので、わざわざパーツを一つずつ自分で作って(コーディングして)、そのクラスをnewする必要がなくなります。単純に言えば再利用性が高いということです。

また、ManagerクラスはPartを扱うクラスではあるものの、実際のPart(例で言うと、SamplePartやSamplePart2)については知らなくて構わないので、パーツとマネージャの依存関係が低いと言えます。

関連パターン
「Flyweightパターン」(勉強中)
「Mementoパターン」(勉強中)
Compositeパターン」Compositeで複雑な構造のインスタンスを作る場合にはprototypeを利用すると良いかもしれない(と本には書かれている)
Decoratorパターン」Decoratorで複雑な構造のインスタンスを作る場合にはprototypeを利用すると良いかもしれない(と本には書かれている)
「Commandパターン」(勉強中)

*1 例としては、グラフィックエディタなどでユーザがマウス操作によって作り上げた図形を再び作りたい場合。プログラミングでこれを作るのは大変なので、ユーザ操作によって作られた図形インスタンスをあらかじめ保存しておいて、それを作りたいときにコピーする(と本には書かれている)
*2 F/W frameworkの略称
*3 本当はclone()の実装についていろいろ説明したいが、本筋から離れるので割愛。気になる人は「Effective Java」のp.54にある「項目11 cloneを注意してオーバーライドする」を参照されたし

2010年5月9日日曜日

Singletonパターン

Singletonパターンとは、システム内でとあるクラスのインスタンスを「たった1つ」しか作らない(作りたくない)ようにするためのパターンです。このパターンを実現することで、指定したクラスのインスタンスが『絶対に』1つしか無いことを保証します。

Singletonにするクラスは以下のようになります。
public class SingletonSample {
    private static SingletonSample instance = new SingletonSample();

    /**
     * デフォルトコンストラクタ
     * 外部から使用されないようにprivateにしている
     */
    private SingletonSample() {}

    public static SingletonSample getInstance() {
        return instance;
    }
}
このクラスのように、Singletonを実現する場合は
  • 自身のインスタンスを持つprivateフィールドを用意する。
  • デフォルトコンストラクタにprivateをつける。
  • インスタンスを取得するメソッドを用意する。
となります。

Mainクラス(SingletonSampleクラスを利用するクラス)では以下のようにgetInstance()を呼べば良いです。
SingletonSample obj1 = SingletonSample.getInstance();

ちなみに、以下のようにMainクラスでSingletonSampleインスタンスを作るコードを入れると、
SingletonSample obj1 = new SingletonSample();




上の通り、コンパイルエラーになります。デフォルトコンストラクタがprivateで外部から利用できないようにしているので当たり前ですね。

また、getInstance()を何度呼んでも同じインスタンスなのかをチェックしてみます。以下のように、getInstance()を2回呼んで、それぞれの変数(obj1とobj2)は同じインスタンスを指しているのかをチェックします。(オブジェクト型を==で比較した場合、それは同一のインスタンスかどうか、正確に言うと、そのインスタンスが置かれているメモリ上の番地が同じかをチェックします。番地が同じならば同じインスタンスを指しているということになり、trueと判定されます)

SingletonSample obj1 = SingletonSample.getInstance();
    SingletonSample obj2 = SingletonSample.getInstance();
    if (obj1 == obj2) {
        System.out.println("same");
    } else {
        System.out.println("different");
    }
実行結果は以下の通りです。



Singletonパターンの悪い実装例も挙げておきます。
    private static SingletonSample instance;

    /**
     * デフォルトコンストラクタ
     * 外部から使用されないようにprivateにしている
     */
    private SingletonSample() {}

    public static SingletonSample getInstance() {
        if (instance == null) {
            instance = new SingletonSample();
        }
        return instance;
    }

上記では、SingletonSampleのインスタンスをフィールドで生成するのではなく、getInstance()が呼ばれた際にnullであるかどうかを判定して、もしnullならばその時に生成する方法を取っています。がしかし、複数の異なるスレッドから「ほぼ同時に」getInstance()を呼び出した場合、SingletonSampleのインスタンスが複数生成される可能性があります。

Javaでは、クラスに初めてアクセスした際にそのクラスの初期化が行われる(当然、その初期化は最初の1回のみ)ので、フィールドでインスタンスを生成するようにしておけば、複数生成されることは無いということになります。

注意:悪い実装例で、getInstance()にsynchronizedをつける方法も考えられるが、同期化コストが高いので推奨しません(と、wikipediaに書かれている)。スレッドのことをよく知らないので、そのコストがどれだけ高いのか分かりません(´・ω・`)

関連パターン
以下のパターンはインスタンスが1つである場合が多い

2010年5月2日日曜日

Factory Methodパターン

Factory Methodパターンとは、インスタンス生成を前回のTemplate Methodパターンで構成したパターンになります。インスタンス生成を行う(まさしく工場と言える)Factoryクラスと生成したものを表すProductクラスをフレームワークとして用意し、具体的な処理はそれぞれを継承して定義します。

例えば、工場では商品を作り、ある処理を行うというフローがある時には
public abstract class Factory {
    /**
     * 商品を作る.
     * @param name 商品名
     */
    public final Product create(String name) {
        Product p = createProduct(name);
        doSomething(p);
        return p;
    }
    
    protected abstract Product createProduct(String name);
    protected abstract void doSomething(Product p);
}

のようにしておきます。具体的な処理はこのクラスを継承して、
public class SampleFactory extends Factory {
    protected Product createProduct(name){
        return new SampleProduct(name);
    }
    protected void doSomething(Product p) {
        ・・・
    }
}

とします。Mainとなるクラス(Factoryを呼ぶクラス)は以下のような処理があれば良いです。
Factory factory = new SampleFactory();
Product product = factory.create("sample");

SampleProductクラスはProductクラスを継承したクラス(具体的な商品を表すクラス)です。

こうすることで、どんな商品が登場しても(Factory、Productクラスを継承したクラスがどんなに増えても)「商品を作って、ある処理を行う」というフローが変わりません。また、フレームワーク側は上記のようなSampleFactoryクラスやSampleProductクラスに依存しません。言い換えると、新商品が登場してもフレームワークを修正する必要がないということです。

関連パターン
「Template Methodパターン」Factory MethodパターンはTemplate Methodパターンの応用例
Singletonパターン」FactoryクラスはSingletonの場合が多い。
Compositeパターン」製品がcomposite(製品の中にある製品が含まれる)になる場合がある。
「Iteratorパターン」Iteratorインスタンス作成にはFactory Methodパターンが使われることがある。

Template Methodパターン

Template Methodパターンとは、文字通り、テンプレートとなるメソッドを用意して利用するパターンです。具体的には、内部に抽象メソッド(Javaの場合、abstractをつけたメソッド)をいくつか呼び出しているテンプレートメソッドを用意し、、抽象メソッドの具体的な処理をサブクラスで決めます。もちろん、サブクラスによって抽象メソッドの処理内容は違う可能性があるわけですが、どのように実装しても大きな流れはテンプレートメソッドで組み立てた通りになります。

例えば、テンプレートのあるクラスは
public abstract class AbstractSample {
    protected abstract void start();
    protected abstract void finish();
    public final void execute() {
        start();
        System.out.println("execute");
        finish();
    }
}

になります。execute()がテンプレートメソッドです。start()とfinishi()はサブクラスで以下のように定義します。
public class Sample extends AbstractSample {
    protected void start() {
        System.out.println("--- Start ---");
    }

    protected void finish() {
        System.out.println("--- Stop ---");
    }
}

別のサブクラスでは以下のようにしてみます。
public class AnotherSample extends AbstractSample {
    protected void start() {
        System.out.println("*** スタート ***");
    }

    protected void finish() {
        System.out.println("*** 終了 ***");
    }
}

Mainクラスは以下のようにしてみました。
public class Main {
    public static void main(String[] args) {
        AbstractSample sample = new Sample();
        sample.execute();
        AbstractSample anotherSample = new AnotherSample();
        anotherSample.execute();
    }
}

実行結果はこんな感じです。








このようにすることで、

  • サブクラスをいくつ作っても、大きな流れはテンプレートメソッドの通り(ロジック共通化)
  • サブクラスをいくつ作っても、呼び出すメソッドは同じ名前のメソッド

といった点が挙げられます。具体的な処理はサブクラスに決めてもらわないといけないとは言え、抽象クラスで処理を形作れることが、抽象クラスの存在意義と言えるでしょう。

注意:本に付属のサンプルコードで、Linux向けのものは文字コードがEUC_JPになっているため、コンパイル時に場合によっては警告が出るかもしれません。その時は、コンパイル時にencodingオプションを入れて

%> javac -encoding EUC_JP *.java

とすること。

関連パターン
「Factory Methodパターン」 Template Methodパターンをインスタンス生成に応用した例
Strategyパターン」Template Methodパターンでは継承を利用してプログラム動作を変更するのに対し、Strategyパターンでは移譲を利用してプログラム動作を変更する。