所以我在学校学习设计模式。今天有人告诉我“原型”设计模式。
我一定是错过了什么,因为我看不到它的好处。我在网上看到有人说它比使用更快,new
但这没有意义;在某些时候,无论新对象如何创建,都需要为其分配内存。
这种模式不是和“鸡还是蛋”的问题一样吗?由于原型模式本质上只是克隆对象,因此在某些时候必须自己创建原始对象(即不克隆)。这意味着我需要准备好要克隆的每个对象的现有副本?
谁能解释这种模式的用途是什么?
所以我在学校学习设计模式。今天有人告诉我“原型”设计模式。
我一定是错过了什么,因为我看不到它的好处。我在网上看到有人说它比使用更快,new
但这没有意义;在某些时候,无论新对象如何创建,都需要为其分配内存。
这种模式不是和“鸡还是蛋”的问题一样吗?由于原型模式本质上只是克隆对象,因此在某些时候必须自己创建原始对象(即不克隆)。这意味着我需要准备好要克隆的每个对象的现有副本?
谁能解释这种模式的用途是什么?
原型模式有一些好处,例如:
例如,假设您的程序使用从通过网络检索到的大多数不变信息中解析的数据创建的对象。无需在每次创建新对象时检索数据并重新解析它,原型模式可用于在需要新对象时简单地复制原始对象。
另外,假设该对象可能具有占用大量内存的数据,例如表示图像的数据。使用写时复制样式继承可以减少内存,其中显示原始的、未重复的数据,直到代码尝试更改该数据。然后,新数据将屏蔽以引用原始数据。
原型模式是一种基于克隆预配置对象的创建模式。这个想法是您选择一个为默认配置或在某些特定用例的范围内配置的对象,然后克隆该对象并根据您的确切需求进行配置。
当所需的配置繁琐时,该模式对于删除一堆样板代码很有用。我认为 Prototypes 是一个预设对象,你可以在其中保存一堆状态作为新的起点。
这里的许多其他答案都谈到了克隆已配置对象的成本节约,但我想扩展原型模式的另一个“点”。在某些语言中,类被视为一等对象,您可以配置客户端在运行时创建的对象类型,只需向其传递类名即可。在像 C++ 这样的语言中,类不被视为一等对象,原型模式允许您实现相同的效果。
例如,假设我们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);
您可能想知道,使用这种方式,原型模式为您购买了与工厂方法模式相同的东西,那么为什么不直接使用呢?因为工厂方法模式需要创建者类的层次结构来反映正在创建的产品的层次结构;即我们需要一个MozzarellaSticksWithKetchupCreator
带有make()
方法的,一个HockeyPuckHamburgerWithSoggyFriesCreator
带有make()
方法的,等等。因此,您可以将原型模式简单地视为减轻工厂方法模式经常引入的代码冗余的一种方法。
这个论点来自Design Patterns: Elements of Reusable Object-Oriented Software,又名“Gang of Four”一书。
如果您想创建一个对象,但又不想经历进行网络或数据库调用的昂贵的对象创建过程,那么请使用原型模式。只需创建对象的副本并对其进行更改。
使用原型模式完全取决于您的问题。在大多数情况下,克隆和创建新对象之间没有任何区别。 但是,如果您在构造函数中或在设置属性时执行一些复杂或耗时的操作,并且必须执行复杂且耗时的操作,原型模式将对我们有所帮助。因为将对象从旧实例复制到新实例更容易并且性能更高。(深度克隆)。所以这种模式更适合那些状态长期不改变的对象。在使用此模式之前,请彻底分析您的问题。
如果您有要求,您需要在其中填充或使用包含 Object repeatable 的相同数据
和
无法从现有对象构建,例如 [使用网络流构建对象] 或
构建一个对象是耗时的[构建一个大对象,通过从数据库中获取数据] 然后使用这个设计模式,因为在这个副本中创建了现有的对象,这个副本将不同于原始对象并且可以使用就像原来的一样。
与抽象工厂模式相比,通过使用原型模式,您不必有大的工厂层次结构,只需要有大的产品层次结构。