所以我正在学习学校的设计模式。今天我被告知'Prototype'设计模式。
我必须遗漏一些东西,因为我没有看到它带来的好处。我见过网上的人说它比使用new
要快,但这没有意义;在某些时候,无论新对象是如何创建的,都需要为它分配内存。
这种模式是否与“鸡肉或鸡蛋”问题在同一个圈子中运行?由于Prototype模式本质上只是克隆对象,因此在某些时候必须自己创建原始对象(即未克隆)。这意味着我需要有一个我想克隆的每个对象的现有副本已经可以克隆了吗?
任何人都可以解释这种模式的用途吗?
答案 0 :(得分:40)
原型模式有一些好处,例如:
例如,假设您的程序使用的对象是根据通过网络检索的杂色不变信息解析的数据创建的。每次创建新对象时,不是检索数据并重新解析数据,原型模式可以用来在需要新对象时简单复制原始对象。
另外,假设对象可能包含占用大量内存的数据,例如表示图像的数据。通过使用写时复制样式继承可以减少内存,其中显示原始的,不重复的数据,直到代码尝试更改该数据。然后,新数据将屏蔽以引用原始数据。
答案 1 :(得分:27)
Prototype模式是基于克隆预配置对象的创建模式。我们的想法是,您选择一个为某个特定用例的默认或球场配置的对象,然后克隆此对象并配置您的确切需求。
当需要的配置很繁琐时,该模式对于删除一堆样板代码非常有用。我认为Prototypes是一个预设对象,你将一堆状态保存为一个新的起点。
答案 2 :(得分:2)
这里的许多其他答案都涉及到克隆已配置对象所节省的成本,但是我想扩展一下原型模式的另一个“要点”。在某些将类视为第一类对象的语言中,您可以通过简单地向其传递类名来配置客户端在运行时创建哪种类型的对象。在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模式相同的东西,那么为什么不仅仅使用它呢?因为“工厂方法”模式将需要创建者类的层次结构,而这些创建者类必须与正在创建的产品的层次结构相仿;也就是说,我们需要使用MozzarellaSticksWithKetchupCreator
方法的make()
,使用HockeyPuckHamburgerWithSoggyFriesCreator
方法的make()
,依此类推。因此,您可以简单地将“原型”模式视为减轻“工厂方法”模式经常引入的代码冗余的一种方法。
此论点摘自《设计模式:可重用的面向对象软件的元素》 ,也就是“四人帮”一书。
答案 3 :(得分:1)
如果要创建对象但不想通过进行网络或数据库调用的昂贵对象创建过程,则使用原型模式。只需创建对象的副本并对其进行更改即可。
答案 4 :(得分:0)
如果您有需求,则需要填充或使用包含Object repeatable
的相同数据和
无法从现有对象构建[使用网络流构建对象] 或
构建一个Object非常耗时[通过从数据库中获取数据来构建一个大对象]然后使用这个设计模式,就像在此复制创建现有对象一样,此副本将与原始对象不同,可以像原始一样使用。
答案 5 :(得分:0)
与抽象工厂模式相比,通过使用原型模式,您不必拥有大的工厂层次结构,只需要一个大的产品层次结构。
答案 6 :(得分:0)
使用原型模式完全取决于您的问题。在大多数常见情况下,克隆和创建新对象之间没有任何区别。 但是如果您在构造函数中或在设置属性时执行一些复杂或耗时的操作并且必须执行复杂且耗时的操作,原型模式将帮助我们。因为将对象从旧实例复制到新实例更容易并且性能更高。(深度克隆)。所以这种模式更适合于长时间不改变状态的对象。 在使用此模式之前,请彻底分析您的问题。