41

我正在学习不同的设计模式,我有一种强烈的感觉,我错过了理解这个特定模式的重要部分(或部分)。

在我查看过的所有网站和 GoF 书中,我看到了克隆方法。据我了解,当我们需要该对象的不同版本时,我们可以克隆某种类型的对象,但我们不希望必须使用“new”命令(如在 Java 中)手动创建每个对象。这可以隐藏它的具体实现。因此,当我们克隆时,我们可以稍微调整克隆并使其成为我们需要的,而不必知道如何以艰难的方式最初创建该对象。这是我的想法正确吗?

我还被告知,这可以减少子类化,从而减少您需要创建的类的数量。我不太明白这部分。有人可以帮我掌握这个吗?

我的最后一个问题是关于抽象工厂(甚至工厂方法)模式。这些工厂模式和原型模式感觉就像他们试图在创建新对象时隐藏具体实现。什么时候选择其中一个是个好主意?

谢谢你们!

4

3 回答 3

62

原型模式

原型导致克隆的对象与原始对象不同。在克隆时,原始状态与克隆相同。此后,每个对象都可能经历状态变化。您可以将其视为类似复印原件,然后在几个地方修改复印件。

例子

  • DVD 复制:复制主 dvd 以创建多个副本
  • 报告对象:考虑一个包含要传递到 GUI 的已处理信息的报告对象。原始报告包含按升序排列的数据。现在,使用这种模式可以创建一个类似的报告,但数据按降序排序。

好处

  • 性能:克隆(使用MemberwiseClone)比重新创建新对象(使用new operator)要便宜得多。请注意,需要覆盖MemberwiseClose()才能执行深层复制。
  • 对象可以非常动态地克隆,无需任何预先实例化。第一个创建的对象可以在应用程序执行中的任何时间创建,并且可以在任何时间提前进行进一步的复制。

何时使用

  • 在运行时指定要实例化的类时,例如,通过动态加载。
  • 当类的实例可以具有仅有的几种不同状态组合之一时。安装相应数量的原型并克隆它们可能更方便,而不是手动实例化类,每次都具有适当的状态。

与工厂模式的比较

原型模式允许对象在不知道它们的类或如何创建它们的任何细节的情况下创建自定义对象。因此,正是在这一方面,它看起来很像工厂方法模式。在这两种模式中,客户端可以创建任何派生类对象,而无需了解它们自己的结构。

但是这两种模式之间的区别在于,Factory Method专注于创建一个不存在的对象类型的对象作为 a fresh creation(通过了解 Creator 类的确切子类型)。该Prototype模式使用类本身,尤其是派生类进行self duplication操作。


工厂方法模式

在这种模式中,客户端(或消费者)向创建者(或工厂)请求来自类层次结构的特定类型的对象。工厂类的 Creator 方法将特定对象的创建委托给派生类,并返回客户端要求的类型的类的对象。实质上,您有一个单一的联系点来创建类层次结构的多个对象。

您可以将其视为前往机票柜台(控制器)并通过提供您对机票类型(头等舱、行政或经济舱)的偏好来索取机票。用户并不关心如何生成机票,即使在对象表示中头等舱和经济舱机票都是从基本机票类别派生的。

何时使用

  • 灵活性很重要(低耦合)
  • 对象可以在子类中扩展
  • 选择一个子类而不是另一个子类有一个特定的原因——这个逻辑构成了工厂方法的一部分。
  • 客户端将责任委托给并行层次结构中的子类。


抽象工厂模式

抽象工厂比工厂方法模式更高(更抽象)。在这种情况下,可以不只是一个工厂,而是多个工厂,略有不同。它负责创建属于类层次结构系列的对象,而不仅仅是单个类层次结构。

一个特定的工厂类已经存在。但是工厂的方法会略有不同。每个方法都可以产生一个实例。客户端可以选择合适的方法并获取实例。

如果以基于MVC的完美架构设计为例,客户端将是业务控制器类,而具体产品将都是业务实体。工厂是辅助(帮助)控制器。他们与来自业务控制器的请求相关联。

何时使用

  • 该系统预计将独立于其产品的创建方式。它甚至可能期望独立于产品的组成和表示方式。术语产品适用于客户端开发人员需要通过调用其方法来使用的最终结果对象。
  • 该系统应可配置为多个产品系列之一。所以家庭的实际选择不会在编码时而是在以后的配置时。
  • 该系列产品旨在始终协同工作。
  • 该创建是针对产品库的。这里更关心的是相关的接口而不是实现。
于 2011-04-21T04:23:57.737 回答
28

你已经得到了原型模式的外观。

它如何减少子类化

假设您正在制作 MineCraft,并且您正在为每种不同类型的方块(例如泥土、石头等)使用原型模式。所有原型对象实际上都属于同一个类Block,但是每个对象都设置了不同的属性,因此它的外观和行为都不同,例如:

prototypes.dirt = new Block;
prototypes.dirt.texture = new Image("dirt.jpg");
prototypes.dirt.hardness = 1;

prototypes.stone = new Block;
prototypes.stone.texture = new Image("stone.jpg");
prototypes.stone.hardness = 9;

因此,不要在您将编写new DirtBlockor的地方进行子类化,new StoneBlock而是编写prototypes.dirt.clone()or prototypes.stone.clone()。不需要子类化,但如果需要,您仍然可以选择子类化。

与工厂模式的区别

至于什么时候选择原型模式而不是工厂模式,我能想到的有两种不同的地方:

  1. 您可以迭代原型列表,但不能迭代抽象工厂上的所有方法^。继续上面的代码,您可以像这样创建一个随机块:

    prototypes.allValues().objectAtIndex(rand() % prototypes.size()).clone();

    如果您使用工厂方法制作块,则很难获得随机块。

  2. 在创建对象的成本很高但复制成本很低的情况下,原型模式会更有效率。例如,采用这个工厂方法:

    Image loadUserImage() { 
        //loads from disk. will be slow
        return new JPEGImage("path/to/user/image.jpg"); 
    }
    

    如果要重复调用此方法,则使用这样的原型会更有效:

    Image loadUserImage() {
        //copy in memory. will be fast
        return userImagePrototype.clone();
    }
    


^ 这是一个善意的谎言,因为您实际上可以根据您使用的语言迭代方法,但迭代数组仍然可能是更好的解决方案,因为它没有反射/内省复杂。

于 2011-04-21T04:34:40.600 回答
15

在我看来,使用原型的效率提升是值得怀疑的。不会有效率提升,因为在大多数语言中,clone 方法本身会执行对 new 的调用,以构造自身的新对象实例。

我看到使用原型模式的唯一好处是方便,您知道克隆将为您提供对象的精确副本,这使您不必自己将新对象的属性设置为相同的值,并且可能会遇到困难带深拷贝。

于 2012-11-27T00:45:48.910 回答