关于原型模式的问题

41

我正在学习不同的设计模式,但我强烈感觉在理解这个特定的模式时缺少了一个重要的部分(或多个部分)。

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

我还被告知,这可以减少子类化,从而减少需要制作的类的数量。我不太理解这一部分。能否有人帮助我理解?

我的最后一个问题涉及抽象工厂(甚至工厂方法)模式。这些工厂模式和原型模式感觉都试图在创建新对象时隐藏具体实现。选择其中之一是一个好主意吗?

谢谢大家!

3个回答

63

原型模式

示例

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

优点

  • 性能:克隆(使用MemberwiseClone)比全新创建对象(使用new operator)要便宜得多。请注意,需要重写MemberwiseClose()来执行深层复制。
  • 对象可以非常动态地进行克隆,而不需要坚持预先实例化。第一个创建的对象可以在应用程序执行的任何时候创建,并且进一步的复制可以在之后的任何时间进行。

何时使用

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

与工厂模式的比较

Prototype模式允许对象创建定制化的对象,而无需知道它们的类或任何创建它们的细节。因此,从这个方面来看,它与工厂方法模式非常相似。在这两种模式中,客户端可以创建任何派生类对象,而不知道它们自己的结构。
但是两种模式之间的区别在于,Factory Method专注于作为新创建的一种不存在的对象类型创建一个对象(通过了解Creator类的确切子类型)。而Prototype模式使用类本身,特别是用于自我复制操作的派生类。

工厂方法模式

可以将其视为去机票柜台(控制器)并通过提供您对票务类型的偏好(头等舱,行政或经济舱)来请求一张机票。用户不关心票是如何生成的,即使在对象表示中,头等舱和经济舱机票都源自基础机票类。

使用时机

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


抽象工厂模式

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

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

如果以基于MVC的完美架构设计为例,客户端将是业务控制器类,而具体产品将全部是业务实体。工厂是辅助(帮助)控制器。它们与业务控制器的请求一起工作。

使用时机

  • 预期该系统独立于其产品的创建方式。甚至可能期望产品的组成和表示方式也是独立的。术语“产品”适用于最终结果对象,客户端开发人员需要通过调用其方法来使用。
  • 该系统应该配置为多个产品族之一。因此,实际的族选择不会在编码时进行,而是在稍后的配置时间进行。
  • 产品族旨在始终一起工作。
  • 创建是针对产品库的。这里更关心的是相关接口而不是实现。

1
我很感激花费时间进行这个精彩的总结。谢谢! - Sobiaholic

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 DirtBlocknew StoneBlock,您可以编写 prototypes.dirt.clone()prototypes.stone.clone()。不需要进行子类化,但如果需要,仍然可以选择进行子类化。

与工厂模式的区别

至于何时选择原型模式而不是工厂模式,我能想到两种情况它们之间的不同:
  1. You can iterate over a list of prototypes, but you can't iterate over all the methods on an abstract factory^. Continuing from the code above, you could create a random block like so:

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

    If you were using the factory method to make blocks, it would be harder to get a random block.

  2. Where creation of an object is expensive, but copying is cheap, the prototype pattern will be more efficient. For example, take this factory method:

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

    If this method is going to be called repeatedly, it would be more efficient to use a prototype like so:

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

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



1
那么一个用途可能是,例如使用原型模式实现(深度)拷贝构造函数? - dragan.stepanovic

16

在我看来,使用原型模式提高效率的作用值得质疑。这是因为在大多数编程语言中,克隆方法本身执行了一个调用new的操作来构造自己的新对象实例,因此不会有效率提升。

我认为使用原型模式的唯一好处只在于方便性。你知道克隆将给你一个对象的完全副本,从而使你不必自己设置新对象的属性值,并且可能会遇到深拷贝的困难。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接