Hedley

Stay Hungry, Stay Foolish.

Java 如何实现 Prototype Pattern

看到 Software Architecture Design Patterns in JavaCREATIONAL PATTERNS: Prototype 1章节,忽感一阵恍惚。之前写负责任再谈 Callback 与 Delegation 时提到了 Java 不支持 Delegation,但是这篇设计模式却好像写如何在 Java 中实现 Delegation。此中涉及了 Object.clone() 方法、Cloneable 接口,真真一团乱麻。

Prototype Pattern

先看 Prototype PatternWiki,一开始提到的便是 Not to be confused with Prototype-based programmingDelegation 正是 Prototype-based programming 的语言特性2。总之,Prototype Pattern 是创建对象的一种方法,Delegation 是语言的一种天赋。

clone() && Cloneable Interface

了解 clone 之前,看 Effective Java, Item 11: Override clone judiciously 是必修课!

随便翻看几个 Java 实现 Prototype Pattern 的例子『戳这里戳这里戳这里』,也包括最开始提到的 CREATIONAL PATTERNS: Prototype,都是通过 Object 基类中的 clone() 方法直接或间接实现的。

clone() 方法是原生方法,它类似 new,会生成新对象,也是一种“构造方法”,所以对构造方法的一些约束对其同样适用3

clone() 方法是 protected 方法,想要调用之还需要绑定 Cloneable 接口。这个奇葩设定很恶心,虽说约定优于配置,但这种约定实在是太不优雅。按照地球人的理解,一个接口应该提供一种服务,Cloneable 接口提供 clone() 方法天经地义,但又由于其特殊性,clone() 方法的原生实现 Java 需要自己提供,而接口声明的方法又不能给出一个默认实现4,然后这个 clone() 方法便写在了 Object 里,Cloneable 接口只是作为一个标识接口用:所有实现了 Cloneable 的类需要重写 clone() 方法,通过 super.clone() 来调用 Object.clone() 的原生实现,然后根据需要再做一些适当的调整。

所谓适当的调整,是因为 Objectclone() 方法只是 shallow copy,如此 clone 出来的对象,其包含的 mutable 的引用还是同一个引用,并非副本。

By convention, the object returned by this method should be independent of this object (which is being cloned). To achieve this independence, it may be necessary to modify one or more fields of the object returned by super.clone before returning it. Typically, this means copying any mutable objects that comprise the internal “deep structure” of the object being cloned and replacing the references to these objects with references to the copies. If a class contains only primitive fields or references to immutable objects, then it is usually the case that no fields in the object returned by super.clone need to be modified.

Object.clone() 说明

了解了这些之后,你以为就可以妥妥的使用 clone() 了吗?还要注意这些限制

  • All classes that implement Cloneable should override clone with a public method whose return type is the class itself.
  • If you override the clone method in a nonfinal class, you should return an object obtained by invoking super.clone. If all of a class’s superclasses obey this rule, then invoking super.clone will eventually invoke Object’s clone method, creating an instance of the right class.
  • In effect, the clone method functions as another constructor; you must ensure that it does no harm to the original object and that it properly establishes invariants on the clone.
  • Like a constructor, a clone method should not invoke any nonfinal methods on the clone under construction.
  • The clone architecture is incompatible with normal use of final fields referring to mutable objects.
  • Object’s clone method is declared to throw CloneNotSupportedException, but overriding clone methods can omit this declaration. Public clone methods should omit it because methods that don’t throw checked exceptions are easier to use.
  • If you decide to make a thread-safe class implement Cloneable, remember that its clone method must be properly synchronized just like any other method.
  • Given all of the problems associated with Cloneable, it’s safe to say that other interfaces should not extend it, and that classes designed for inheritance (Item 17) should not implement it.

鉴于以上种种,我们可以很保守的说:珍爱生命,远离 clone()

Copy Constructor || Copy Factory

EJ 给出了一种替代解决方案:copy constructor or copy factory。它对比 clone() 简直好处多多:不需要受奇葩语意约束、不会和 final 标识的属性冲突、不抛出无意义的异常、不需要类型转换。

copy constructor 接受的参数是其自身实现的接口类型,它们其实类属于 conversion constructor。注意 HashMapCollection 的构造方法里,都有这种 conversion constructor,但是它们都是 shallow copy

序列化以及其他

Is Object deserialization a proper way to implement Prototype pattern in Java? 细致讨论了通过序列化实现 Prototype Pattern 的可能性。结论是可以,但代价是巨大的,还不如 clone 呢!其实 XML、JSON 都可以作为实现原型模式的手段,只要能得到一个对象的副本就行了呗。问题中的每一个回答每一个评论都值得细细品味。

kluge [英][k’lu:dʒ] [美][k’lu:dʒ]    n.由不配套的元件拼凑而成的计算机,异机种系统

Deep clone utility recomendation. 给出了实现 deep clone 的一个开源类库 The cloning library,此贴子的回答评论同样精彩。略看了一眼 The cloning library 的简介和评价,期待度较高,貌似是通过反射实现的,等有空研究一下代码再表。




  1. Page 95.

  2. language feature

  3. 主要就是这一条:Never call overridable methods from constructors, either directly or indirectly.

  4. JKD8 可以做到,咋不早出这个特性嘞