我正在学习设计模式。今天我了解到了“原型”设计模式。
可能是我漏看了什么,因为我没有看到它的好处。我在网上看到有人说它比使用 new
更快,但这不太合理;无论如何创建新对象,都需要分配内存。
这个模式难道不会遇到“先有鸡还是先有蛋”的问题吗?由于原型模式本质上只是克隆对象,在某个时候必须首先创建出原始对象(即非克隆对象)。这意味着我需要已经准备就绪要克隆的每个对象的现有副本吗?
有人能解释一下这个模式的用途吗?
我正在学习设计模式。今天我了解到了“原型”设计模式。
可能是我漏看了什么,因为我没有看到它的好处。我在网上看到有人说它比使用 new
更快,但这不太合理;无论如何创建新对象,都需要分配内存。
这个模式难道不会遇到“先有鸡还是先有蛋”的问题吗?由于原型模式本质上只是克隆对象,在某个时候必须首先创建出原始对象(即非克隆对象)。这意味着我需要已经准备就绪要克隆的每个对象的现有副本吗?
有人能解释一下这个模式的用途吗?
原型模式有一些好处,例如:
例如,假设您的程序使用从网络检索的大部分不变信息解析的数据创建对象。与每次创建新对象时检索数据和重新解析数据不同,可以使用原型模式来在需要新对象时简单地复制原始对象。
还假设该对象可能具有使用大量内存的数据,例如表示图像的数据。可以通过使用写时复制风格继承来减少内存使用,其中显示原始未复制的数据,直到代码尝试更改该数据。然后,新数据将掩盖对原始数据的引用。
原型模式是一种基于克隆预配置对象的创建模式。其思想在于选择一个已经配置好默认或接近某个特定用例的对象,然后克隆此对象并根据您的确切需求进行配置。
该模式对于消除大量样板代码非常有用,当所需的配置会很繁琐时。我将原型看作预设对象,您可以将其保存为新起点的一堆状态。
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
变成一个炫耀的名人厨师。这意味着他/她必须在 prepareMeal()
中 new
不同的菜肴。我们希望修改该方法,以便可以将由 Chef
new
的菜肴类型作为参数指定。在其他支持类作为一等对象的语言中,我们可以简单地将类名作为参数传递给方法。但在 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()
方法等等。因此,你可以将原型模式简单地看作是缓解工厂方法模式经常引入的代码冗余的一种方式。
这个论点摘自《设计模式:可复用面向对象软件的基础》(即“四人组”书)。
使用原型模式完全取决于你的问题。在大多数情况下,克隆和创建新对象没有任何区别。 但是如果你在构造函数或设置属性时执行了一些复杂或耗时的操作,并且必须执行复杂和耗时的操作,则原型模式将对我们有所帮助。因为从旧实例复制对象到新实例更容易,而且性能更高(深度克隆)。因此,该模式更适用于状态长时间不变的对象。 在使用此模式之前,请彻底分析你的问题。
如果您有这样的需求,需要填充或使用包含可重复对象的相同数据
但是
从现有对象构建不可能,例如[使用网络流构建对象],或者
构建一个对象很耗时[通过从数据库获取数据构建大型对象],那么请使用此设计模式。在此模式中,创建了一个现有对象的副本,该副本与原始对象不同,并且可以像原始对象一样使用。