原型模式:用原型实例指定建立对象的种类,而且经过拷贝这些原型对象建立新的对象。java
原型模式其实就是从一个对象建立另一个可复制的对象,并且不须要知道任何建立的细节。(最经常使用的就是基于流的深复制)ide
Java语言自己支持原始原型模式。全部的JavaBean都继承自Java.lang.Object,而Object类提供一个clone方法,能够将一个JavaBean对象复制一份。可是这个JavaBean必须实现一个表示接口Cloneable,代表这个JavaBean支持复制。若是一个对象没有实现这个接口而调用clone()方法,Java编译器会抛出CloneNotSupportedException异常。测试
对象就是类的实例。通常状况下,一个类被实例化时,此类的全部成员,包括变量和方法,都被复制到属于此数据类型的一个新的实例中取。好比:this
User user = new User();
上面的语句作了以下的事情:spa
(1)建立了一个User类型的变量,称为userprototype
(2)创建了一个User类的对象线程
(3)使变量user指到这个对象上。设计
能够将上面分红下面两步:code
User user; user = new User();
第一行创建了一个变量叫作user,能够指到User类对象上。但在上面第一行结束时并无指到它上面,只是在第二行才真正指到这样的一个对象上。对象
第二行建立了一个对象。当对象刚被建立时是一个无名对象,随后这个对象才有了一个名字user。
也有可能一个对象被建立以后就没有名字,永远保持无名状态。
User user = new User(); user.setBirthDay(new Date());
如上面,咱们知道参数的传递是经过引用传递,因此只是将地址传下去。接受参数的setBirthDay 方法也不关心它接收的对象有没有名字。
所以,对象的建立与它们的引用是独立的
clone()方法将对象复制了一份并返还给调用者。所谓“复制”的含义与clone()方法是怎么实现的。通常而言,clone()方法知足如下的描述:
(1)对任何的对象x,都有:x.clone()!=x。换言之,克隆对象与原对象不是同一个对象。
(2)对任何的对象x,都有:x.clone().getClass() == x.getClass(),换言之,克隆对象与原对象的类型同样。
(3)若是对象x的equals()方法定义其恰当的话,那么x.clone().equals(x)应当成立的。
在JAVA语言的API中,凡是提供了clone()方法的类,都知足上面的这些条件。JAVA语言的设计师在设计本身的clone()方法时,也应当遵照着三个条件。通常来讲,上面的三个条件中的前两个是必需的,而第三个是可选的。
原型模式有两种表现形式:(1)简单形式;(2)登记形式。
(1)简单形式:
三个角色:
(1)客户(Client)角色:客户类提出建立对象的请求。
(2)抽象原型(Prototype)角色:这是一个抽象角色,一般由一个Java接口或Java抽象类实现。此角色给出全部的具体原型类所需的接口。
(3)具体原型(Concrete Prototype)角色:被复制的对象。此角色须要实现抽象的原型角色所要求的接口。
以下:
package cn.qlq.prototype; public interface Prototype { Prototype clone(); }
package cn.qlq.prototype; public class ConcretePrototype1 implements Prototype { public Prototype clone() { // 没有属性就再也不复制值了 Prototype prototype = new ConcretePrototype1(); return prototype; } }
package cn.qlq.prototype; public class ConcretePrototype2 implements Prototype { public Prototype clone() { // 没有属性就再也不复制值了 Prototype prototype = new ConcretePrototype2(); return prototype; } }
客户端代码:
package cn.qlq.prototype; public class Client { private Prototype prototype; public Client(Prototype prototype) { this.prototype = prototype; } public void operation(Prototype example) { Prototype copyPrototype = prototype.clone(); } }
(2)登记形式
做为原型模式的第二种形式,它比简单形式多了一个原型管理器(PrototypeManager)角色,该角色的做用是:建立具体原型类的对象,并记录每个被建立的对象。
原型管理器角色保持一个集合,做为对全部原型对象的登记,这个角色提供必要的方法,供外界增长新的原型对象和取得已经登记过的原型对象。
package cn.qlq.prototype; import java.util.HashMap; import java.util.Map; public class PrototypeManager { private static Map<String, Prototype> prototypes = new HashMap<String, Prototype>(); private PrototypeManager() { } public synchronized static void setPrototype(String prototypeId, Prototype prototype) { prototypes.put(prototypeId, prototype); } public synchronized static void removePrototype(String prototypeId) { prototypes.remove(prototypeId); } public synchronized static Prototype getPrototype(String prototypeId) throws Exception { Prototype prototype = prototypes.get(prototypeId); if (prototype == null) { throw new Exception("不存在"); } return prototype; } }
测试代码:
package cn.qlq.prototype; public class Client { public static void main(String[] args) throws Exception { Prototype p1 = new ConcretePrototype1(); PrototypeManager.setPrototype("p1", p1); // 获取原型来建立对象而且复制一份 Prototype p3 = PrototypeManager.getPrototype("p1").clone(); System.out.println(p1 == p3); } }
两种形式的比较:
若是须要建立的原型对象数目较少并且固定的话,能够采起第一种形式也就是简单形式的原始模型模式。在这种状况下,原型对象的引用由客户端本身保存。
若是要建立的原型对象数目不固定的话,采用登记形式。保存原对象的引用由管理器角色执行。在复制一个原型对象以前,客户端能够查看管理员对象是否已经有一个知足要求的的原型对象,若是有直接返回,若是没有客户端就须要自行复制此原型对象。
浅复制
只负责克隆按值传递的数据(好比基本数据类型、String类型),而不复制它所引用的对象,换言之,全部的对其余对象的引用都仍然指向原来的对象。
深复制
除了浅度克隆要克隆的值外,还负责克隆引用类型的数据。那些引用其余对象的变量将指向被复制过的新对象,而再也不是原有的那些被引用的对象。换言之,深度克隆把要复制的对象所引用的对象都复制了一遍,而这种对被引用到的对象的复制叫作间接复制。
深度克隆要深刻到多少层,是一个不易肯定的问题。在决定以深度克隆的方式复制一个对象的时候,必须决定对间接复制的对象时采起浅度克隆仍是继续采用深度克隆。所以,在采起深度克隆时,须要决定多深才算深。此外,在深度克隆的过程当中,极可能会出现循环引用的问题,必须当心处理。
把对象写到流里的过程称为串行化,在Java中又称为"冷冻"或者"腌咸菜";而把对象从流里读出来的并行化过程则叫作"解冻"或者"回鲜"过程。应当指出的是,写到流里的是对象的一个拷贝,而原对象仍然存在于JVM里面,所以"腌咸菜"只是对象的一个拷贝。
在Java语言里深度克隆一个对象,经常能够先使对象实现Serializable接口,而后把对象(实际上只是对象的拷贝)写到一个流里(序列化),再从流里读回来(反序列化),即可以重建对象。
以下代码:
public static <T> T cloneObj(T obj) { T retVal = null; try { // 将对象写入流中 ByteArrayOutputStream baos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(baos); oos.writeObject(obj); // 从流中读出对象 ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bais); retVal = (T) ois.readObject(); } catch (Exception e) { e.printStackTrace(); } return retVal; }
这样作的前提就是对象以及对象内部全部引用到的对象都是可序列化的,不然,就须要仔细考察那些不可序列化的对象能否设成transient,从而将之排除在复制过程以外。
浅复制显然比深复制更容易实现,由于Java语言的全部类都会继承一个clone()方法,而这个clone()方法所作的就是浅复制。
有一些对象,好比线程(Thread)对象或Socket对象,是不能简单复制或共享的。不论是使用浅复制仍是深复制,只要涉及这样的间接对象,就必须把间接对象设成transient而不予复制;或者由程序自行建立出至关的同种对象,权且当作复制件使用。
以下测试代码:
User是根对象,其内部引用类也要实现Serializable接口,user的name属性用transient关键字修饰不会被序列化。
package cn.qlq.prototype; import java.io.Serializable; public class User implements Serializable { private Address address; private int id; private transient String name; private String sex; private String job; private String health; private String BMI; private int height; private int weight; public User() { super(); } public User(Address address, int id, String name, String sex, String job, String health, String bMI, int height, int weight) { super(); this.address = address; this.id = id; this.name = name; this.sex = sex; this.job = job; this.health = health; BMI = bMI; this.height = height; this.weight = weight; } public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getSex() { return sex; } public void setSex(String sex) { this.sex = sex; } public String getJob() { return job; } public void setJob(String job) { this.job = job; } public String getHealth() { return health; } public void setHealth(String health) { this.health = health; } public String getBMI() { return BMI; } public void setBMI(String bMI) { BMI = bMI; } public int getHeight() { return height; } public void setHeight(int height) { this.height = height; } public int getWeight() { return weight; } public void setWeight(int weight) { this.weight = weight; } public Address getAddress() { return address; } public void setAddress(Address address) { this.address = address; } @Override public String toString() { return "User [address=" + address + ", id=" + id + ", name=" + name + ", sex=" + sex + ", job=" + job + ", health=" + health + ", BMI=" + BMI + ", height=" + height + ", weight=" + weight + "]"; } } class Address implements Serializable { private String province; private String country; public Address(String province, String country) { super(); this.province = province; this.country = country; } public String getProvince() { return province; } public void setProvince(String province) { this.province = province; } public String getCountry() { return country; } public void setCountry(String country) { this.country = country; } @Override public String toString() { return "Address [province=" + province + ", country=" + country + "]"; } }
public static void main(String[] args) throws Exception { User user = new User(); user.setName("张三"); user.setBMI("BMI"); user.setHealth("健康"); user.setHeight(50); user.setId(1); Address address = new Address("山西", "cn"); user.setAddress(address); // 克隆对象并修改属性后打印 User cloneedUser = cloneObj(user); cloneedUser.getAddress().setCountry("北京"); System.out.println(cloneedUser); System.out.println("========"); System.out.println(user); }
结果:(name属性没有被复制上,并且其address是深复制)
User [address=Address [province=山西, country=北京], id=1, name=null, sex=null, job=null, health=健康, BMI=BMI, height=50, weight=0]
========
User [address=Address [province=山西, country=cn], id=1, name=张三, sex=null, job=null, health=健康, BMI=BMI, height=50, weight=0]
原型模式容许在运行时动态改变具体的实现类型。原型模式能够在运行期间,由客户来注册符合原型接口的实现类型,也能够动态地改变具体的实现类型,看起来接口没有任何变化,但其实运行的已是另一个类实例了。由于克隆一个原型就相似于实例化一个类。
原型模式最主要的缺点是每个类都必须配备一个克隆方法。配备克隆方法须要对类的功能进行通盘考虑,这对于全新的类来讲不是很难,而对于已经有的类不必定很容易,特别是当一个类引用不支持序列化的间接对象,或者引用含有循环结构的时候。
假设一个系统的产品类是动态加载的,并且产品具备必定的等级结构。这个时候若是使用工厂模式的话,工厂模式就得具备必定的结构。而产品类的等级结构发生变化,工厂类的等级结构就得随之变化。这对于产品结构常常可能会变的系统来讲,采用工厂模式就不方便。
这时候就能够采用原型模式,给每一个产品装配一个克隆方法(大多数时候只需给产品类的根类装配克隆方法)。
这种模式使用的最多的就是深复制,项目中也没用到这种模式。