原型模式属于对象的创建模式,用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象。原型模式实际上就是从一个对象创建另一个新的对象,使新的对象有具有原对象的特征。
那么在java语言中,有几种方式可以创建对象呢?
1.我们可以使用new操作符新建一个对象。
2.我们可以使用Object的clone()方法复制一个对象。
new操作符的本意是分配内存:程序执行到new操作符时, 首先去看new操作符后面的类型,根据这个类型去创建相应的空间。分配内存之后,再调用构造函数,填充对象的各个域,即完成对象的初始化。对象创建好后,把它的引用(地址)发布到外部,通过这个引用去操作对象。
clone()第一步和new相似,也是分配内存:调用clone()方法时,分配的内存和源对象(即调用clone方法的对象)相同,然后再使用原对象中对应的各个域,填充新对象的域, 填充完成之后,clone方法返回,一个新的相同的对象被创建,然后同样把这个新对象的引用发布到外部,通过这个引用去操作对象。
现在我们就分别采用两种方式,来理解“原型模式”。
1、使用new操作符“复制”原有对象:此种方式要求先实例化一个对象,然后将原有对象的属性全部“复制”到新对象上。
这里以汽车原型为例,分别“复制”宝马汽车和奥迪汽车。 先上UML类图:
package com.pattern.prototype.one;
public interface CarPrototype {
public CarPrototype clone();//克隆汽车
public void setName(String name);//设置汽车名字
public void setHeight(int height);//设置汽车高度
public void setColor(String color);//设置汽车颜色
}
package com.pattern.prototype.one;
/**
* 宝马汽车原型
* @author
*
*/
public class BaoMaCarPrototype implements CarPrototype{
private String name;
private int height;
private String color;
@Override
public CarPrototype clone() {
CarPrototype car=new BaoMaCarPrototype();
car.setName(this.name);
car.setHeight(this.height);
car.setColor(this.color);
return car;
}
@Override
public void setName(String name) {
this.name=name;
}
@Override
public void setHeight(int height) {
this.height=height;
}
@Override
public void setColor(String color) {
this.color=color;
}
@Override
public String toString() {
return "BaoMaCarPrototype [name=" + name + ", height=" + height + ", color=" + color + "]";
}
}
package com.pattern.prototype.one;
/**
* 奥迪汽车原型
* @author
*
*/
public class AoDiCarPrototype implements CarPrototype{
private String name;
private int height;
private String color;
@Override
public CarPrototype clone() {
CarPrototype car=new AoDiCarPrototype();
car.setName(this.name);
car.setHeight(this.height);
car.setColor(this.color);
return car;
}
@Override
public void setName(String name) {
this.name=name;
}
@Override
public void setHeight(int height) {
this.height=height;
}
@Override
public void setColor(String color) {
this.color=color;
}
@Override
public String toString() {
return "AoDiCarPrototype [name=" + name + ", height=" + height + ", color=" + color + "]";
}
}
为了方便理解“原型”模式的“复制”过程,这里定义一个汽车原型管理类,作为复制原型的”窗口“。
package com.pattern.prototype.one;
import java.util.HashMap;
import java.util.Map;
/**
* 汽车原型管理类
* @author
*
*/
public class CarPrototypeManager {
/**
* 用来存储汽车原型信息
*/
private static Map<String,CarPrototype> carPrototypeMap = new HashMap<String,CarPrototype>();
/**
* 私有化构造方法,避免外界调用创建实例
*/
private CarPrototypeManager(){}
/**
* 设置汽车原型编号和对应的原型汽车
* @param carPrototypeId 汽车原型编号
* @param car 原型汽车
*/
public synchronized static void setPrototypeCar(String carPrototypeId , CarPrototype car){
carPrototypeMap.put(carPrototypeId, car);
}
/**
* 根据汽车原型编号获取对应的原型汽车
* @param carPrototypeId 汽车原型编号
* @return
* @throws Exception
*/
public synchronized static CarPrototype getPrototypeCar(String carPrototypeId) throws Exception{
CarPrototype prototype = carPrototypeMap.get(carPrototypeId);
if(prototype == null){
throw new Exception("您想要获取的汽车原型不存在!!!");
}
return prototype;
}
}
汽车原型客户端测试类:
package com.pattern.prototype.one;
public class PrototypeClientTest1 {
public static void main(String[] args){
try {
CarPrototype car1 = new BaoMaCarPrototype();
car1.setName("宝马x1新款");
car1.setHeight(1911);//1911mm
car1.setColor("白色");
System.out.println("car1原型-----------"+car1.toString());
CarPrototypeManager.setPrototypeCar("BM1", car1);
CarPrototype carClone1=CarPrototypeManager.getPrototypeCar("BM1").clone();
System.out.println("car1克隆版-----------"+carClone1.toString()+"\n");
System.out.println("car1和克隆版是同一个吗?-----------"+(car1==carClone1)+"\n");
CarPrototype car2 = new AoDiCarPrototype();
car2.setName("奥迪Q5");
car2.setHeight(2000);//2000mm
car2.setColor("黑色");
System.out.println("car2原型-----------"+car2.toString());
CarPrototypeManager.setPrototypeCar("Q5", car2);
CarPrototype carClone2=CarPrototypeManager.getPrototypeCar("Q5").clone();
System.out.println("car2克隆版-----------"+carClone2.toString()+"\n");
System.out.println("car2和克隆版是同一个吗?-----------"+(car2==carClone2)+"\n");
} catch (Exception e) {
e.printStackTrace();
}
}
}
测试结果如下:
从测试结果可以看出,通过对象"复制”,分别从宝马汽车原型和奥迪汽车原型得到了新的对象,且将原型的属性全部“复制”,似乎已经达到了“原型模式”的要求。但是大家可以思考下,为什么克隆出来的对象和原型对象不是同一个呢?
2、使用clone()方法复制原有对象:利用java中Cloneable接口,实现Object的clone()方法复制对象和属性。
在Java中,终极父类Object将Clone方法作为了所有类应具有的基本功能,并且Java也提供了Cloneable接口。那么利用clone()方法实现原型方式就变得简单多了。
原型模式的核心就是类图中的原型类CarPrototype。Prototype类需要具备以下两个条件:
- 实现Cloneable接口。在java语言有一个Cloneable接口,它的作用只有一个,就是在运行时通知虚拟机可以安全地在实现了此接口的类上使用clone方法。在java虚拟机中,只有实现了这个接口的类才可以被拷贝,否则在运行时会抛出CloneNotSupportedException异常。
- 重写Object类中的clone方法。Java中,所有类的父类都是Object类,Object类中有一个clone方法,作用是返回对象的一个拷贝,但是其作用域protected类型的,一般的类无法调用,因此,Prototype类需要将clone方法的作用域修改为public类型。
原型模式是一种比较简单的模式,也非常容易理解,实现一个接口,重写一个方法即完成了原型模式。在实际应用中,原型模式很少单独出现。经常与其他模式混用,原型类Prototype也常用抽象类来替代。
package com.pattern.prototype.two;
/**
* 汽车原型
* @author
*
*/
public class CarPrototype implements Cloneable{
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
package com.pattern.prototype.two;
/**
* 宝马汽车原型
* @author
*
*/
public class BaoMaCarPrototype extends CarPrototype{
private String name;
private int height;
private String color;
private Wheel wheel;
@Override
protected Object clone() throws CloneNotSupportedException {
BaoMaCarPrototype car=(BaoMaCarPrototype)super.clone();
//car.wheel=(Wheel)car.getWheel().clone();
return car;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getHeight() {
return height;
}
public void setHeight(int height) {
this.height = height;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
public Wheel getWheel() {
return wheel;
}
public void setWheel(Wheel wheel) {
this.wheel = wheel;
}
@Override
public String toString() {
return "Car[name=" + name + ", height=" + height + ", color=" + color + ", wheel=" + wheel + "]";
}
}
package com.pattern.prototype.two;
/**
* 车轮
* @author
*
*/
public class Wheel{
private String wheelColor;//车轮颜色
private int number;//车轮数量
private Axle axle;//轮轴
public String getWheelColor() {
return wheelColor;
}
public void setWheelColor(String wheelColor) {
this.wheelColor = wheelColor;
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
public Axle getAxle() {
return axle;
}
public void setAxle(Axle axle) {
this.axle = axle;
}
@Override
public String toString() {
return "Wheel [wheelColor=" + wheelColor + ", number=" + number + ", axle=" + axle + "]";
}
}
package com.pattern.prototype.two;
/**
* 轮轴
* @author
*
*/
public class Axle {
private int axNumber;//数量
private int width;//宽度
private final String color="黑色";
public int getAxNumber() {
return axNumber;
}
public void setAxNumber(int axNumber) {
this.axNumber = axNumber;
}
public int getWidth() {
return width;
}
public void setWidth(int width) {
this.width = width;
}
public String getColor() {
return color;
}
@Override
public String toString() {
return "Axle [axNumber=" + axNumber + ", width=" + width + ", color=" + color + "]";
}
}
客户端测试类:
package com.pattern.prototype.two;
public class PrototypeClientTest2 {
public static void main(String[] args){
try {
Axle axle=new Axle();
axle.setAxNumber(10);
axle.setWidth(50);
Wheel wheel=new Wheel();
wheel.setAxle(axle);
wheel.setNumber(4);
wheel.setWheelColor("灰色");
BaoMaCarPrototype car1 = new BaoMaCarPrototype();
car1.setName("宝马x1新款");
car1.setHeight(1911);//1911mm
car1.setColor("白色");
car1.setWheel(wheel);
System.out.println("car1原型-----------"+car1+"\n");
//按照汽车原型克隆一个
BaoMaCarPrototype carP1=(BaoMaCarPrototype)car1.clone();
System.out.println("car1克隆版-----------"+carP1+"\n");
System.out.println("car1和克隆版是同一个吗?-----------"+(car1==carP1)+"\n");
} catch (Exception e) {
e.printStackTrace();
}
}
}
测试结果如下:
使用原型模式创建对象比直接new一个对象在性能上要好的多,因为Object类的clone方法是一个本地方法,它直接操作内存中的二进制流,特别是复制大对象时,性能的差别非常明显。
以上两个例子中,对象都已经完成了“复制”功能,包括对象中的“属性”都被复制。但是为什么克隆出来的对象和原有对象不是同一个呢?这就是我下面要讲的深拷贝和浅拷贝的问题。
深拷贝与浅拷贝【深克隆与浅克隆】:Object类的clone方法只会拷贝对象中的基本的数据类型(8种基本数据类型byte,char,short,int,long,float,double,boolean),浅拷贝只负责拷贝按值传递的数据,而不复制它所引用的对象,换言之,所有的对其他对象的引用都仍然指向原来的对象。如果要实现深拷贝,必须将原型模式中的数组、容器对象、引用对象等另行拷贝,那些引用其他对象的变量将指向被复制过的新对象,而不再是原有的那些被引用的原型对象。
深拷贝要深入到多少层,是一个不易确定的问题。在决定以深拷贝的方式复制一个对象的时候,必须决定对间接复制的对象时采取浅度克隆还是继续采用深度克隆。而在java 中,所有的Object都有clone()方法,而这个克隆就是浅度克隆。
如果要实现深度克隆,就需要利用序列化实现:把对象写到流里的过程是序列化(Serialization)过程;而把对象从流中读出来的过程则叫反序列化(Deserialization)过程。应当指出的是,写到流里的是对象的一个拷贝,而原对象仍然存在于JVM里面。在Java语言里深度克隆一个对象,常常可以先使对象实现Serializable接口,然后把对象(实际上只是对象的拷贝)写到一个流里(序列化),再从流里读回来(反序列化),便可以重建对象。这样做的前提就是对象以及对象内部所有引用到的对象都是可序列化的,否则,就需要仔细考察那些不可序列化的对象可否设成transient,从而将之排除在复制过程之外。
关于深拷贝和浅拷贝,有疑惑请看这篇博客:http://blog.youkuaiyun.com/zhangjg_blog/article/details/18369201
源码下载:http://download.youkuaiyun.com/download/pelifymeng2/9994100