1. 序章

このチュートリアルでは、作成デザインパターンの1つであるプロトタイプパターンについて学習します。 最初にこのパターンを説明し、次にJavaでの実装に進みます。

また、その長所と短所のいくつかについても説明します。

2. プロトタイプパターン

プロトタイプパターンは通常、クラス(プロトタイプ)のインスタンスがあり、プロトタイプをコピーするだけで新しいオブジェクトを作成したい場合に、で使用されます。

このパターンをよりよく理解するために、アナロジーを使用してみましょう。

一部のゲームでは、背景に木や建物が必要です。 キャラクターが動くたびに、新しい木や建物を作成して画面にレンダリングする必要がないことに気付くかもしれません。

したがって、最初にツリーのインスタンスを作成します。 次に、このインスタンス(プロトタイプ)から必要な数のツリーを作成し、それらの位置を更新できます。 また、ゲームの新しいレベルに合わせて木の色を変更することもできます。

プロトタイプパターンは非常に似ています。 新しいオブジェクトを作成する代わりに、プロトタイプインスタンスのクローンを作成する必要があります。

3. UML図

この図では、クライアントがプロトタイプに自分自身のクローンを作成してオブジェクトを作成するように指示していることがわかります。 プロトタイプはインターフェースであり、それ自体を複製するためのメソッドを宣言します。 ConcretePrototype1およびConcretePrototype2は、自身を複製する操作を実装します。

4. 実装

このパターンをJavaで実装する方法の1つは、 clone()メソッドを使用することです。 これを行うには、Cloneableインターフェイスを実装します。

クローンを作成する場合は、浅いコピーを作成するか深いコピーを作成するかを決定する必要があります。 最終的に、それは要件に要約されます。

たとえば、クラスにプリミティブ不変フィールドのみが含まれている場合、シャローコピーを使用できます。

可変フィールドへの参照が含まれている場合は、ディープコピーを選択する必要があります。 コピーコンストラクターまたはシリアル化と逆シリアル化を使用してこれを行うことができます。

前述の例を取り上げて、Cloneableインターフェースを使用せずにプロトタイプパターンを適用する方法を見てみましょう。 これを行うために、abstractメソッド‘copy’を使用してTreeというabstractクラスを作成しましょう。

public abstract class Tree {
    
    // ...
    public abstract Tree copy();
    
}

ここで、PlasticTreePineTreeというTreeの2つの異なる実装があるとします。

public class PlasticTree extends Tree {

    // ...

    @Override
    public Tree copy() {
        PlasticTree plasticTreeClone = new PlasticTree(this.getMass(), this.getHeight());
        plasticTreeClone.setPosition(this.getPosition());
        return plasticTreeClone;
    }

}
public class PineTree extends Tree {
    // ...

    @Override
    public Tree copy() {
        PineTree pineTreeClone = new PineTree(this.getMass(), this.getHeight());
        pineTreeClone.setPosition(this.getPosition());
        return pineTreeClone;
    }
}

したがって、ここでは、 Tree を拡張し、 copy メソッドを実装するクラスが、自身のコピーを作成するためのプロトタイプとして機能できることがわかります。

プロトタイプパターンでは、具象クラスに依存せずにオブジェクトのコピーを作成することもできます。 木のリストがあり、それらのコピーを作成したいとします。 ポリモーフィズムにより、木の種類を知らなくても簡単に複数のコピーを作成できます。

5. テスト

それをテストしてみましょう:

public class TreePrototypesUnitTest {

    @Test
    public void givenAPlasticTreePrototypeWhenClonedThenCreateA_Clone() {
        // ...

        PlasticTree plasticTree = new PlasticTree(mass, height);
        plasticTree.setPosition(position);
        PlasticTree anotherPlasticTree = (PlasticTree) plasticTree.copy();
        anotherPlasticTree.setPosition(otherPosition);

        assertEquals(position, plasticTree.getPosition());
        assertEquals(otherPosition, anotherPlasticTree.getPosition());
    }
}

ツリーがプロトタイプから複製され、PlasticTreeの2つの異なるインスタンスがあることがわかります。 クローン内の位置を更新し、他の値を保持しました。

次に、ツリーのリストのクローンを作成しましょう。

@Test
public void givenA_ListOfTreesWhenClonedThenCreateListOfClones() {

    // create instances of PlasticTree and PineTree

    List<Tree> trees = Arrays.asList(plasticTree, pineTree);
    List<Tree> treeClones = trees.stream().map(Tree::copy).collect(toList());

    // ...

    assertEquals(height, plasticTreeClone.getHeight());
    assertEquals(position, plasticTreeClone.getPosition());
}

Treeの具体的な実装に依存することなくここでリストのディープコピーを実行できることに注意してください。

6. 長所と短所

このパターンは、新しいオブジェクトが既存のオブジェクトとわずかに異なる場合に便利です。 場合によっては、インスタンスはクラス内の状態の組み合わせがわずかしかない場合があります。 したがって、新しいインスタンスを作成する代わりに、事前に適切な状態のインスタンスを作成し、必要なときにいつでもそれらを複製することができます

場合によっては、状態のみが異なるサブクラスに遭遇することがあります。 初期状態でプロトタイプを作成し、それらを複製することで、これらのサブクラスを排除できます。

プロトタイプパターンは、他のすべてのデザインパターンと同様に、適切な場合にのみ使用する必要があります。 オブジェクトのクローンを作成しているため、クラスが多いとプロセスが複雑になり、混乱が生じる可能性があります。 さらに、循環参照を持つクラスのクローンを作成することは困難です。

7. 結論

このチュートリアルでは、プロトタイプパターンの主要な概念を学び、Javaで実装する方法を学びました。 また、その長所と短所のいくつかについても説明しました。

いつものように、この記事のソースコードはGithubから入手できます。