一、动机与定义
之前学习原型模式一直以为原型模式目的是为了方便的创建同样或相似对象,用复制对象的方式替换new的方式,还研究了深克隆和浅克隆。近期细致看了GOF的设计模式。发现原型模式的本意并不不过复制对象这么简单。
复制对象确实是一方面,当我们须要大量相似。甚至同样对象的时候,除了一个个的new之外。还能够依据一个原型,直接复制出很多其它的对象。可是假设把原型模式觉得仅仅是复制对象这么简单就错了。
创建型模式主要讲怎样创建对象,通常包括何时创建。谁来创建。怎么创建等。
GOF书里面写的意图是,用原型实例指定创建对象的种类,而且通过拷贝这些原型对象创建新的对象。也就是说原型模式应该理解成先指定好要创建的对象种类,也就是指定对象类型,再通过拷贝方式创建对象。
此时再看原型模式定义就更清晰了:用原型实例指定创建对象的种类,而且通过复制这些原型创建新的对象。
原型模式的目的是创建对象。通过复制的方式来创建对象,复制不过一种方式、一种方法、或者说一种手段,而不是目的。重点在创建对象上,而不是复制上,假设把重点放到复制对象上。那就变成了研究深克隆,浅克隆这些东西。他们不过创建对象的一种方法而已,没有这些克隆,我们自己实现克隆也全然能够。
二、结构与类图
原型模式类图例如以下:
Prototype:声明一个复制自身的接口,能够是接口或类。事实上复制自身不一定非要全部状态必须一样。后面会有提到。
ConcretePrototype:被复制的类。实现了复制自身的操作。
Client:让一个原型复制自身,从而创建一个新对象。
可是在Java中提供了一个本地clone方法来高效克隆对象,为了让全部类可以使用通用的clone方法并且不受单一继承的影响,就把clone放到到了Object中。假设想使用,仅仅须要实现Cloneable这个标记接口即可。这个仅仅能说Java提供了非常便利的方法让我们直接使用原型模式(相似的还有非常多,比方观察者java.util.Observer等)。而Java默认提供的这个clone仅仅会克隆基本类型和String,也就是通常所说的浅克隆。要是全部对象都克隆,就要自己实现深克隆了。
三、适用场景及效果(优缺点)
先说我之前的理解,之前一直认为原型模式有两个重点。一是通过复制取代new了,所以适用场景一般是new一个对象比較困难、代价大。复制方便简单时使用;二是复制对象能保存对象状态,所以须要保留对象状态时使用。通常有以下几个适用场景:
1、创建对象成本较大时,直接复制一个对象就非常easy。这个成本指非常多方面,如时间花费较长、资源占用较多、初始化过程繁琐等等。
2、系统须要对象状态时,比方某个对象有10个状态(或者说属性),我须要一个和这个对象9个状态同样。仅仅有1个状态不同一时候,原型模式很方便(有些情况又一次创建可能甚至无法获取从前的状态了),举个样例,我要创建一个资源訪问配置。配置好了资源位置、读取方式、缓存大小、訪问权限。訪问password等等,当訪问还有一个资源时,仅仅有位置不同。其它的全然同样,此时就能够直接复制一个对象,然后改动一下资源位置状态就可以。
3、一个对象相应多个改动者的情况。当一个对象供多个改动者使用时。比方多线程改动一个对象时。能够考虑使用原型模式创建多个同样对象供调用者使用,从而避免资源竞争,加锁等(这个要看你的需求,假设目的是共享资源訪问。就不能这样了)。
事实上这些也没问题,可是和GOF说的另一定差距,GOF说了4种适用场景:
1、当一个系统应该独立于它的产品创建、构成和表示时。要使用Prototype模式。
这句话范围非常广泛。事实上依赖倒置原则下的一个解决的方法,调用者不能依赖详细。仅仅能依赖抽象,可是调用者不知道详细产品是哪个时怎么办。工厂模式是直接向工厂要一个对象。建造者是向导演类要一个对象,单例是向单例类要一个对象。而原型模式是从一个已有的对象上要对象,要一个和你一模一样的对象。
2、当要实例化的类是在执行时刻指定时。比如。通过动态装载。这个指须要动态创建对象类型。如开发游戏中的地面类型,有草地、雪地、水泥地等等,程序先创建好各种土地的原型,执行时依据參数来推断详细要创建哪种类型土地,找到类型相应的原型后,复制就可以。
3、为了避免创建一个与产品类层次平行的工厂类层次。
这个就是有时候能够取代抽象工厂模式,不是不须要工厂和工厂方法了,而是把工厂方法放到自己的clone方法中。把自身当成工厂了,这样就能降低工厂类。尽管clone方法名字是复制自身。事实上不一定就非得复制一个状态全然一模一样的对象,有时候仅仅要对象类型一样就可以,也不一定就要复制一个新的对象。有时候把自己return出去也是能够的(和单例结合使用)。
4、当一个类的实例仅仅能有几个不同状态组合中的一种时。
建立对应数目的原型并克隆它们可能比每次用合适的状态手工实例化该类更方便一些。这个是解决“仅仅有固定的几类,每类对象可能有多个时”创建对象的情况,比方中国象棋中的棋子,仅仅有固定7类(车马象士将炮兵)。不会新增其它类的,每类有多个对象(兵有5个。炮有2个等),我们能够先把每种棋子都初始化好(7类棋子),用到的时候复制一个自己即可了。
//棋子 public class Piece { // 类型 private int type; // 名字 private String name; // 状态 private String state; // 颜色 private String color; // 其它属性...... }
当然。假设种类固定,数量也固定,那能够考虑使用泛型了。
在说说使用后的效果,也就是长处:
1、和工厂、建造者模式一样,封装底层实现,依赖抽象产品,隐藏了具体的产品,降低代码复杂度(类少了)等等。这里就不具体说了。
2、执行时刻添加和删除产品。这个指用户使用不同产品时,无需创建新的产品类,能够使用一个属性状态等来控制对象类型。如上面样例中的棋子。就不是必需为每种棋子创建一个类,仅仅须要一个type就能控制是哪类棋子,调用者使用时。能够自行动态创建和降低产品类。
也降低了类数量,简化了代码。只是这个要看实际情况,对象种类都特别相似的情况下才干够。
3、改变值以指定新的对象。和上面类似。仅仅须要改动产品属性值就能创建一个新的产品,很方便。
4、改变结构以指定新的对象。这个就是直接一个深克隆创建一个全然新的产品。直接使用。非常方便。比方做汽车轮胎。须要4个。创建1个轮胎成本较大,可是创建1个后,复制4个就非常easy了。
5、降低子类的构造,前面也说了。相比工厂等方法,降低了工厂类。clone方法直接放到了产品里面。新增一个子类,不用添加工厂等,简化了代码。
6、用类动态配置应用,我们能够将一些配置都放到一个对象中,使用时对外提供这个配置对象的副本就可以。这样动态改动配置对象后,不影响旧的对象使用。而新的使用到配置的地方能够用新的配置。
缺点:
1、原型模式缺点也是显而易见的,全部子类都必须实现clone方法,新设计的程序还好。原始程序改造时就麻烦了,逐个给全部类加一个clone方法太难了。
2、无法克隆问题,克隆对象的方式看起来非常好,可是有个问题,有些对象不能克隆怎么办。有些对象克隆起来太复杂怎么办,比方java中的属性使用了finalkeyword,不能2次赋值,就没办法实现深度克隆。另一种情况,内部属性之间是相互引用,循环引用的,此时要实现克隆就非常困难。
3、深度克隆须要复杂的代码,这个没啥好说的。
四、模式扩展
调用者直接复制原型有个缺点,调用者的角色不明白,他须要直到要用哪个原型。原型对象和复制出来的产品之间也可能无法区分(大部分时候不用区分),所以有一种方式引入了原型管理器来管理原型,维护已有原型的清单,调用者使用时会向原型管理器发出请求,获取原型,这样调用者就不须要知道详细原型了,原型和复制出来的产品也能够区分了。
这个是相比其它创建型模式来说的。没有工厂,没有单例。没有导演类。原型模式仅仅须要一个产品原型,就能复制出其它产品。所以很灵活,调用者能够在执行时动态添加和删除产品,而不用先写一堆工厂。单例。建造者这种类。