春のセール

Prototype

別名:Clone、プロトタイプ、クローン

一言でいうと

Prototype プロトタイプ 原型 既存オブジェクトのコピーをそのクラスに依存することなく可能とする 生成に関するデザインパターンの一つです

Prototype デザインパターン

問題

ここにオブジェクトがあって この正確なコピーを作成したいとします どうすればいいでしょう まず 同じクラスの新規オブジェクトを作成する必要があります 次に 元のオブジェクトのすべてのフィールドの値を一つずつ新しいオブジェクトにコピーしていきます

よさそうですね でも落とし穴があります どんなオブジェクトでもこの方法でコピーできるわけではないのです オブジェクトのフィールドの一部は private 非公開 オブジェクトの外からは見えないようになっているかもしれません

「外部で」コビーしてうまく行かない場合

外部からオブジェクトをコピーすることは常に可能とは限りません

この直接的なやり方には もう一つ問題があります 複製を作成するためには オブジェクトのクラスについてよく知っている必要があるので コードはそのクラスに依存するようになります 余分な依存関係なんてへっちゃらという方には もう一つ別の落とし穴があります オブジェクトが従うインターフェースはわかっているが 具象クラスは知らない という場合があります たとえば あるメソッドは パラメーターとして あるインターフェースに従うどんなオブジェクトでも受け取るといった場合です

解決策

このパターンでは クローン 複製 される実際のオブジェクトにクローン作成の作業が任されます クローンをサポートするすべてのオブジェクトに対する共通インターフェースを宣言します このインターフェースを使用すると コードをそのオブジェクトのクラスに密に結合せずにオブジェクトのクローン作成ができます 通常 この手のインターフェースは clone メソッド一つだけからなっています

clone メソッドの実装はすべてのクラスで非常に似ています このメソッドは現在のクラスのオブジェクトを作成し 古いオブジェクトのすべてのフィールド値を新しいクラスに引き継ぎます ほとんどのプログラミング言語では 同じクラスに属するオブジェクトは 他のオブジェクトの非公開フィールドにアクセスできるため 非公開フィールドをコピーすることもできます

クローン作成をサポートするオブジェクトは と呼ばれます オブジェクトに数十のフィールドと数百の可能な構成がある場合 サブクラスを作る代わりにクローン作成が有効な代替手段となるかもしれません

構築済みプロトタイプ

事前に構築されたプロトタイプはサブクラス化の代替手段

仕組み 様々な構成のオブジェクトをあらかじめ作成しておきます これら構成ずみオブジェクトの一つに似たオブジェクトが必要な場合 新しいオブジェクトを一から構築する代わりにプロトタイプをクローンします

現実世界でのたとえ

現実世界では 製品の大量生産を開始する前に 様々なテストを行うためにプロトタイプが使用されます しかし この場合 プロトタイプは実際の生産には使われません 受動的な役割を果たすだけです

細胞分裂

細胞分裂

工業用プロトタイプは本当に自分自身をコピーしないので パターンに非常に近い類似は 細胞分裂 生物学 覚えていますか? のプロセスです 分裂後 同一細胞のペアが形成されます 元のセルはプロトタイプとして機能し コピーの作成に積極的な役割を果たします

構造

基本的な実装

Prototype デザインパターンの構造Prototype デザインパターンの構造
  1. プロトタイプ Prototype インターフェースはクローン作成メソッドを宣言します ほとんどの場合 ただ一つの clone メソッドからできています

  2. 具象プロトタイプ Concrete Prototype のクラスは クローン作成メソッドを実装します 元のオブジェクトのデータをコピーするだけではなく このメソッドはクローン作成時にクローン同士をリンクするとか 再起的依存性を取り払うなどの作業も行うかもしれません

  3. クライアント Client プロトタイプのインターフェースに従うどんなオブジェクトでも そのコピーを生成できます

プロトタイプ・レジストリーの実装

プロトタイプ・レジストリープロトタイプ・レジストリー
  1. プロトタイプ・レジストリー Prototype Registry により よく使われるプロトタイプに簡単にアクセスできます そこには いつでも複製可能な状態の構築ずみオブジェクトが蓄えられています 一番簡単な実装方法は 名前 → プロトタイプ のハッシュマップを使うことです しかし 単に名前に頼る以上の検索方式が必要な場合は もっと堅牢なレジストリーを作ることも可能です

擬似コード

この例では Prototype パターンを使用して コードをクラスに密に結合することなく 幾何学的なオブジェクトの正確なコピーを生成します

Prototype パターン例の構造

クラス階層に属するオブジェクトの集合をクローンする

すべての形状クラスは クローン作成メソッドを持つ同一インターフェースに従っています サブクラスは サブクラス特有のフィールド値を結果のオブジェクトにコピーする前に 親のクローン作成メソッドを呼び出すこともできます

// プロトタイプの基底クラス。
abstract class Shape is
    field X: int
    field Y: int
    field color: string

    // 通常のコンストラクター。
    constructor Shape() is
        // ……

    // プロトタイプのコンストラクター。新鮮なオブジェクトは、既存のオブ
    // ジェクトの値を使って初期化される。
    constructor Shape(source: Shape) is
        this()
        this.X = source.X
        this.Y = source.Y
        this.color = source.color

    // クローン操作は、Shape のサブクラスのいずれかを返す。
    abstract method clone():Shape


// 具象プロトタイプ。クローン作成メソッドは、現クラスのコンストラクターを
// 呼び、引数として現オブジェクトを渡すことにより新規オブジェクトを一度に
// 作成。実際のコピー作業をコンストラクターの中で行い、結果の一貫性を保証。
// コンストラクターは完全なオブジェクトが完成するまで帰らないので、他の
// オブジェクトが部分的に作成されたクローンへの参照を持つことは不可能。
class Rectangle extends Shape is
    field width: int
    field height: int

    constructor Rectangle(source: Rectangle) is
        // 親クラスで定義された非公開フィールドのコピーを行うため、親のコ
        // ンストラクターを呼ぶことは必須。
        super(source)
        this.width = source.width
        this.height = source.height

    method clone():Shape is
        return new Rectangle(this)


class Circle extends Shape is
    field radius: int

    constructor Circle(source: Circle) is
        super(source)
        this.radius = source.radius

    method clone():Shape is
        return new Circle(this)


// クライアント・コードのどこか。
class Application is
    field shapes: array of Shape

    constructor Application() is
        Circle circle = new Circle()
        circle.X = 10
        circle.Y = 10
        circle.radius = 20
        shapes.add(circle)

        Circle anotherCircle = circle.clone()
        shapes.add(anotherCircle)
        // anotherCircle 変数は、circle オブジェクトの完全なコピーを含
        // む。

        Rectangle rectangle = new Rectangle()
        rectangle.width = 10
        rectangle.height = 20
        shapes.add(rectangle)

    method businessLogic() is
        // Prototype はすごい!ここでは、オブジェクトのコピーの作成をそ
        // の型について一切知らずに行う。
        Array shapesCopy = new Array of Shapes.

        // 我々は、shapes 配列内の要素が厳密に何であるかを知らない。わ
        // かっているのは、どれも形状だということだけ。しかし、多相性のお
        // かげで、形状の一つに対して clone メソッドを呼ぶと、プログラム
        // がその本当のクラスが何かをチェックし、そのクラスで定義された適
        // 切な clone メソッドを実行する。一連の単純な Shape オブジェク
        // トではなく、適切なクローンを取得可能なのはこのため。
        foreach (s in shapes) do
            shapesCopy.add(s.clone())

        // shapesCopy 配列には、shape 配列の子要素が正確にコピーされる。

適応性

自分のコードがコピー対象オブジェクトの具象クラスに依存したくない場合に Prototype パターンを使用します

この状況は 自分のコードが何らかのインターフェースを介して渡された外部開発コードからのオブジェクトと動作する場合によく発生します これらのオブジェクトの具象クラスは不明であり 仮に望んでもそれに依存することはできません

Prototype パターンを適応すると クライアントのコードは クローンをサポートするすべてのオブジェクトを扱う一般的なインターフェースを使います このインターフェースにより クライアントのコードは クローン対象オブジェクトの具象クラスから独立したものとなります

オブジェクトの初期化方法のみが異なるサブクラスの数を減らしたい場合に このパターンを使用します

使用する前に手の込んだ設定を必要とする複雑なクラスがあるとします このクラスを設定するには いくつかの共通する方法がありますが それはアプリのあちこちに散らばっています 重複を減らすために 複数のサブクラスを作成し 共通設定コードをサブクラスのコンストラクターに入れます 重複問題を解決しましたが 今度はダミーのサブクラスがたくさんできました

Prototype パターンでは いろいろな方法で事前構築したオブジェクトを使うことができます 何かの構成に一致するサブクラスをインスタンス化する代わりに クライアントは適切なプロトタイプを探してクローンするということができます

実装方法

  1. プロトタイプのインターフェースを作成し そこで clone メソッドを宣言します または 既存のクラス階層がある場合は 階層下のすべてのクラスにメソッドを追加するだけでもかまいません

  2. プロトタイプ・クラスは そのクラスのオブジェクトを引数として受け取る代替コンストラクターを定義しなければなりません コンストラクターは 渡されたオブジェクトから新しく作成されたインスタンスにクラスで定義されたすべてのフィールドの値をコピーする必要があります サブクラスを変更する場合は スーパークラスが非公開フィールドのクローン作成処理できるように親コンストラクターを呼び出す必要があります

    お使いのプログラミング言語がメソッドの多重定義をサポートしていない場合は オブジェクトのデータを複製するための特別なメソッドを定義することになります コンストラクターは new 演算子を呼び出した直後に生成されたオブジェクトを使えるため これを行うためのより便利な場所です

  3. クローン作成メソッドは通常 プロトタイプのクラスの new 演算子を呼び出すだけの一行だけです どのクラスもクローン作成メソッドを new に続けてそのクラス名が来るように上書きしなければなりません そうしないと 親クラスのオブジェクトがクローンされてしまいます

  4. 必要ならば 頻繁に使用されるプロトタイプのカタログを備蓄しておく 集中型プロトタイプ・レジストリーを作成します

    レジストリーは 新しいファクトリー・クラスとして実装することもできますし またはプロトタイプを取得する静的メソッドをプロトタイプの基底クラスに追加することもできます このメソッドは クライアント・コードからメソッドに渡される検索条件に基づいてプロトタイプを検索します 検索条件は単純な文字列によるタグかもしれませんし または複雑な検索パラメータの組み合わせかもしれません 適切なプロトタイプが見つかった後 レジストリーはそれをクローンし クライアントに返します

    最後に サブクラスのコンストラクターへの直接呼び出しを プロトタイプ・レジストリーのファクトリー・メソッドへの呼び出しに置き換えます

長所と短所

  • 具象クラスと密に結合せずにオブジェクトのクローンが可能
  • 構築済みのプロトタイプのクローン作成を使うことにより 初期化コードの重複を削減
  • 複雑なオブジェクトの生成がより便利
  • 複雑なオブジェクトに対する構成の事前設定を扱う上で継承に代わる方法を提供
  • 循環参照のある複雑なオブジェクトのクローン作成は一筋縄ではいかない場合あり

他のパターンとの関係

  • 多くの設計は まず比較的単純でサブクラスによりカスタマイズ可能な Factory Method から始まり 次第に もっと柔軟だが複雑な Abstract FactoryPrototypeBuilder へと発展していきます

  • Abstract Factory クラスは 多くの場合 Factory Methods の集まりですが Prototype を使ってメソッドを書くこともできます

  • Prototype Commands のコピーを履歴に保存する必要がある場合に役立ちます

  • CompositeDecorator を多用する設計に対しては Prototype の使用が有益かもしれません このパターンを適用すると 複雑な構造を初めから再構築するのではなく それをクローンします

  • Prototype は継承に基づいていないので 継承の欠点はありません 一方 Prototype クローンされたオブジェクトの複雑な初期化が必要となります Factory Method は継承に基づいていますが 初期化のステップは必要ありません

  • 場合によっては PrototypeMemento の代わりに使用した方が簡単な場合があります 状態の履歴を保存したいオブジェクトが比較的単純で 他の外部リソースへのリンクを持たないか簡単に再現できる場合に この方法が使えます

  • Abstract Factories Builders Prototypes はどれも Singletons で実装可能です

コード例

Prototype を C# で Prototype を C++ で Prototype を Go で Prototype を Java で Prototype を PHP で Prototype を Python で Prototype を Ruby で Prototype を Rust で Prototype を Swift で Prototype を TypeScript で