ここでの他の回答の多くは、構成済みのオブジェクトを複製することによるコスト削減について述べていますが、Prototype パターンの他の「ポイント」について詳しく説明したいと思います。クラスがファーストクラスのオブジェクトとして扱われる一部の言語では、クラス名を渡すだけで、実行時にクライアントによって作成されるオブジェクトのタイプを構成できます。クラスがファーストクラスのオブジェクトとして扱われない C++ のような言語では、Prototype パターンを使用して同じ効果を得ることができます。
たとえば、Chef
食事を作って提供するレストランがあるとします。Chef
が低賃金で不満を持っているとしましょう。そのため、彼は次のような料理を作ります。
class Chef {
public:
void prepareMeal() const {
MozzarellaSticksWithKetchup* appetizer = new MozzarellaSticksWithKetchup();
// do something with appetizer...
HockeyPuckHamburgerWithSoggyFries* entree = new HockeyPuckHamburgerWithSoggyFries();
// do something with entree...
FreezerBurnedIceCream* dessert = new FreezerBurnedIceCream();
// do something with dessert...
}
};
Chef
ここで、 を派手な有名シェフに変更したいとしましょう。これは、彼/彼女が でnew
別の料理を食べなければならないことを意味しprepareMeal()
ます。new
で取得する食事の種類をChef
パラメータとして指定できるようにメソッドを修正したいと思います。クラスがファースト クラス オブジェクトである他の言語では、クラス名をパラメーターとしてメソッドに渡すだけです。C++ ではこれを行うことができないため、プロトタイプ パターンを利用できます。
class Appetizer {
public:
virtual Appetizer* clone() const = 0;
// ...
};
class Entree {
public:
virtual Entree* clone() const = 0;
// ...
};
class Dessert {
public:
virtual Dessert* clone() const = 0;
// ...
};
class MozzarellaSticksWithKetchup : public Appetizer {
public:
virtual Appetizer* clone() const override { return new MozzarellaSticksWithKetchup(*this); }
// ...
};
class HockeyPuckHamburgerWithSoggyFries : public Entree {
public:
virtual Entree * clone() const override { return new HockeyPuckHamburgerWithSoggyFries(*this); }
// ...
};
class FreezerBurnedIceCream : public Dessert {
public:
virtual Dessert * clone() const override { return new FreezerBurnedIceCream(*this); }
// ...
};
// ...and so on for any other derived Appetizers, Entrees, and Desserts.
class Chef {
public:
void prepareMeal(Appetizer* appetizer_prototype, Entree* entree_prototype, Dessert* dessert_prototype) const {
Appetizer* appetizer = appetizer_prototype->clone();
// do something with appetizer...
Entree* entree = entree_prototype->clone();
// do something with entree...
Dessert* dessert = dessert_prototype->clone();
// do something with dessert...
}
};
clone()
メソッドは派生型のインスタンスを作成しますが、親型へのポインターを返すことに注意してください。これは、別の派生型を使用して作成されるオブジェクトの型を変更できることを意味し、クライアントはその違いを認識しません。この設計によりChef
、プロトタイプのクライアントを構成して、実行時にさまざまな種類の料理を作成できるようになりました。
Chef chef;
// The same underpaid chef from before:
MozzarellaSticksWithKetchup mozzarella_sticks;
HockeyPuckHamburgerWithSoggyFries hamburger;
FreezerBurnedIceCream ice_cream;
chef.prepareMeal(&mozzarella_sticks, &hamburger, &ice_cream);
// An ostentatious celebrity chef:
IranianBelugaCaviar caviar;
LobsterFrittataWithFarmFreshChives lobster;
GoldDustedChocolateCupcake cupcake;
chef.prepareMeal(&caviar, &lobster, &cupcake);
このように使用すると疑問に思うかもしれませんが、Prototype パターンは Factory Method パターンと同じものを購入するので、なぜそれを使用しないのでしょうか? Factory Method パターンでは、作成される製品の階層を反映するクリエーター クラスの階層が必要になるためです。つまり、MozzarellaSticksWithKetchupCreator
with a make()
method 、HockeyPuckHamburgerWithSoggyFriesCreator
with a method などが必要make()
になります。したがって、Prototype パターンは、Factory Method パターンによってしばしば導入されるコードの冗長性を軽減する 1 つの方法と見なすことができます。
この議論は、 Design Patterns: Elements of Reusable Object-Oriented Software、別名「Gang of Four」の本から引き出されています。