原型模式
原型模式(Prototype Pattern)就是可以用某些手段拷贝一个已经存在的对象来获得新对象,即并不是用new
这种正常方式去创建一个对象。
原型模式往往是出于以下两点的考虑:
- 对象的创建过程可能比较复杂,使用原型模式可以避免复杂的构建流程
- 要获得的新对象可能和已经存在的对象有很多共同之处,这时直接拷贝再对不同的地方做修改会更简便
和其它设计模式不太一样,在Java中,原型模式已经在语言级别实现了。只需要直接使用或覆写clone()
来实现深/浅拷贝即可。
我现在知道的创建对象的方式:[1]直接new调用构造器创建,[2]使用反射创建,[3]使用原型模式拷贝。
Java语言本身对此的设计
Cloneable接口
Java中要覆写Object
类的clone()
需要实现的Cloneable
接口是标记型接口(Serializable
也是),内部没有方法和属性。如果不实现该接口,会抛出CloneNotSupportedException
异常,所以覆写的clone()
常常是这样的:
@Override
protected Object clone() throws CloneNotSupportedException {
//...
}
Mixin interface
Cloneable
接口是一个Mixin接口,实现这类接口,就可以为某个类添加一些可选的功能。在Java中形如*able
的接口都是这种接口,Cloneable
、Compareable
、Serializable
等,但Serializable
接口的功能(序列化和反序列化)不需要类自己来实现,其它的则是要具体实现的,如实现了Cloneable
接口就可以覆写Object
的clone()
方法更改其行为。
关于Mixin这一概念,其实它是实现多继承的一种方式,知乎上从各个角度做了很好的解读。
试想因为每个类都继承了Object
基类,如果基类里面的这些方法(clone()
之类)全都设置成抽象的,让每个非抽象类都必须去实现,这样肯定很麻烦,因为多数类根本不需要去覆写他们,他们和类本身要做的事情没什么关系。
但如果把这个方法就放在Cloneable
接口里面,那实现了之后,实现的根本不是Object
的clone()
方法,这样普通的接口没法指示这种特殊的功能,所以需要设计成Mixin接口。
Object的clone()方法
在Object
类中,clone()
方法被设置成protected
类型的。因为Object
不可能预知其子类还具有什么样的成员,所以只能实现浅拷贝,而设置成protected
类型就是想让需要做深拷贝的子类去覆写然后自己调用成员的clone
(成员也要去实现深拷贝)来一层层调用父类的clone()
实现深拷贝。
关于protected
类型,其实并非之前学的那样就是相比包访问权限多个父子类的访问权限,实际上不能访问不同包的父类自己的受保护成员,但super
关键字就解决了这个问题,详细的可以见这里。
为了使用原型模式,在自己实现的clone()
方法上应该将访问权限开到public
,这样就能在外面调用它了。
clone的替代方案
clone()
的使用好复杂,用它实现深拷贝很麻烦,容易遗漏出错。一方面可以使用前面学过的序列化-反序列化方式,另一方面可以使用一些工具类,比如BeanUtils,来实现对象的拷贝。
让拷贝原型和使用者解耦
当要用clone()
拷贝一个对象时,会直接在程序中把这个对象暴露出来,这样(要拷贝它的)使用者就会依赖这个对象,耦合性比较高。
在runoob上提供的例子里增加了一个缓存类,将所有的用来拷贝的对象登记在Map中,并对外提供了不依赖于类(这样不仅对象解耦,类也解耦了)的拷贝访问点,对象统一登记在这个缓存类里,可能会好一些。