这几天胡老师一直在讲关于设计模式的一些内容,自己也下去看了一些跟设计模式相关的书。根据自己的理解:所谓模式,就是一个模板,是经过前人总结的一个经验性的东西,而设计模式就是在设计方面的经验总结。现在大家普遍知道的是Gof总结出来的23种设计模式,这23种模式可以分为三个类别:创建型模式( 关于对象创建的模式)、结构型模式(关于对象的组成以及对象之间的依赖关系的模式)、行为型模式(关于对象行为的模式),这么多种设计模式有一个共同点,它们都是以对象为中心的,也就是以类为中心,这是值得注意的。如果按照一定的逻辑,比如说:按照对象的生命周期,从创建对象开始,再到对象之间的结构关系,最后到对象的使用,这样理解起来整体的框架就有了。
这里主要简单介绍一下关于创建对象过程中的原型模式(Prototype)。
为什么需要原型模式?
首先从具体事件上分析: 在画图工具里,要画圆只需要拖动工具条的画圆工具到绘图区即可,不需要从头开始一点一点地画一个圆,而且如果需要不同大小和颜色的圆,只需要复制一下,然后再修改它们的大小和颜色即可。
再从概念上进行分析: 比如有一个对象,在某一时刻该对象中已经包含了一个有效值,此时可能会需要一个和该对象完全相同的新对象,并且今后对新对象的任何改动都不会影响到原来对象中的值。也就是说,新对象和原来的对象是两个独立的对象,但新对象的初始值是由原来对象确定的。简单的说:就是要得到一个原有对象的拷贝。
什么是原型模式?
原型模式就是通过一个对象来表明要创建的对象类型,然后用复制这个原型对象的方法来创建更多的同类型对象。
原型模式的简单使用方法
①实现Cloneable接口
②复写java.lang.Object类的clone()方法
注意:
a.在Java中,通过赋值语句创建一个对象,则新对象和原来的对象是同一个引用。如果修改一个对象的值,另一个对象也会发生改变。这也是原型模式产生的一个重要原因。
b.Java中的所有类都默认继承java.lang.Object类,在Java.lang.Object类中有一个方法clone(),该方法返回一个Object对象的一个拷贝。
原型模式简单使用的代码示例
通过一个简单的钥匙的案例,来理解原型模式究竟怎么使用。
/**
* 钥匙的原型类
*/
public abstract class KeyPrototype implements Cloneable{
//钥匙长度
private float length;
//钥匙厚度
private float thick;
//钥匙颜色
private String color;
//复制对象
public Object clone(){
Object obj = null;
try {
obj = super.clone();
} catch (Exception e) {
e.printStackTrace();
}
return obj;
}
//获取长度
public float getLength() {
return length;
}
//设置长度
public void setLength(float length) {
this.length = length;
}
//获取厚度
public float getThick() {
return thick;
}
//设置厚度
public void setThick(float thick) {
this.thick = thick;
}
//获取颜色
public String getColor() {
return color;
}
//设置颜色
public void setColor(String color) {
this.color = color;
}
}
/*
* 铜钥匙类
*/
public class CopperKey extends KeyPrototype {
/***
* 构造器
*/
public CopperKey(){
setColor("黄色");
}
}
/**
* 铝钥匙类
*/
public class AluminiumKey extends KeyPrototype{
/**
* 构造器
*/
public AluminiumKey(){
setColor("银色");
}
}
/**
* 客户端测试类
*/
public class Client {
/*
* 主方法
*/
public static void main(String[] args){
//创建一个铜钥匙类
KeyPrototype copperKey = new CopperKey();
//设置铜钥匙的长度
copperKey.setLength(3.1f);
//设置铜钥匙的厚度
copperKey.setThick(0.5f);
//克隆一把铜钥匙
KeyPrototype aluminiumKey = (KeyPrototype)copperKey.clone();
//设置克隆钥匙的颜色
aluminiumKey.setColor("银色");
System.out.println("第1次克隆后钥匙的颜色:"+copperKey.getColor()+" 长度:"+copperKey.getLength()+" 厚度:"+copperKey.getThick());
System.out.println("第1次克隆后钥匙的颜色:"+aluminiumKey.getColor()+" 长度:"+aluminiumKey.getLength()+" 厚度:"+aluminiumKey.getThick());
//创建一个铝钥匙
KeyPrototype aluminiumKey1 = new AluminiumKey();
//设置铝钥匙的长度
aluminiumKey1.setLength(4.0f);
//设置绿钥匙的厚度
aluminiumKey1.setThick(1.0f);
//克隆一把铝钥匙
KeyPrototype copperKey1 = (KeyPrototype)aluminiumKey1.clone();
//设置克隆钥匙的颜色
copperKey1.setColor("黄色");
System.out.println("第2次克隆前钥匙的颜色:"+aluminiumKey1.getColor()+" 长度:"+aluminiumKey1.getLength()+" 厚度:"+aluminiumKey1.getThick());
System.out.println("第2次克隆后钥匙的颜色:"+copperKey1.getColor()+" 长度:"+copperKey1.getLength()+" 厚度:"+copperKey1.getThick());
}
}
在Eclipse中输出结果是:
第1次克隆后钥匙的颜色:黄色 长度:3.1 厚度:0.5
第1次克隆后钥匙的颜色:银色 长度:3.1 厚度:0.5
第2次克隆前钥匙的颜色:银色 长度:4.0 厚度:1.0
第2次克隆后钥匙的颜色:黄色 长度:4.0 厚度:1.0
通过上面的结果,可以看出实现了复制对象的目的。下面再来看一个关于普通狗和克隆狗例子。
/*
* 狗类
*/
public class Dog {
//狗腿数
public int legCounts;
/**
* 构造器
*/
public Dog(int legCounts){
this.legCounts = legCounts;
}
/**
* 改变狗腿数目
*/
public void changeLegCounts(){
this.legCounts *= 2;
}
/**
* 将整型变量转换成字符串的方法
*/
public String toString(){
return Integer.toString(this.legCounts);
}
}
/**
* 实现了克隆接口的狗类
*/
public class DogClone implements Cloneable{
//狗腿数
public int legCounts;
//创建一个狗类,传入参数4
public Dog dog = new Dog(4);
/**
* 克隆的方法
*/
public Object clone(){
//定义一个DogClone对象
DogClone o = null;
try {
o = (DogClone)super.clone();
} catch (Exception e) {
e.printStackTrace();
}
return o;
}
}
/**
* 在客户端进行调用的类
*/
public class CloneMain {
/**
* 主方法
*/
public static void main(String[] args) {
//创建一个克隆狗对象
DogClone dogClone = new DogClone();
//设置克隆狗的狗腿数
dogClone.legCounts = 3;
System.out.println("原来的克隆狗腿数量:"+dogClone.legCounts);
System.out.println("原来的普通狗腿数量:"+dogClone.dog.legCounts);
//克隆一个克隆狗对象
DogClone dogClone1 = (DogClone)dogClone.clone();
//设置克隆的克隆狗腿数量
dogClone1.legCounts = 2;
//创建一个狗对象
Dog dog = dogClone1.dog;
//改变狗腿数量
dog.changeLegCounts();
System.out.println("克隆后原来的克隆狗腿数量:"+dogClone.legCounts);
System.out.println("克隆后原来的普通狗狗腿数量:"+dogClone.dog.legCounts);
System.out.println("克隆狗的克隆狗狗腿数量:"+dogClone1.legCounts);
System.out.println("克隆后的普通狗的狗腿数量:"+dogClone1.dog.legCounts);
}
}
在Eclipse中的输出结果是:
原来的克隆狗腿数量:3
原来的普通狗腿数量:4
克隆后原来的克隆狗腿数量:3
克隆后原来的普通狗狗腿数量:8
克隆狗的克隆狗狗腿数量:2
克隆后的普通狗的狗腿数量:8
可以看到:克隆后原来的普通狗的狗腿数目跟着克隆后的普通狗的狗腿数目变了,它们指向了同一对象。要解决这个问题,普通狗类也要实现Cloneable接口和复写clone()方法,这就是深度克隆。
用深度克隆实现的克隆狗类和普通狗类的测试
/*
* 狗类
*/
public class Dog implements Cloneable{
//狗腿数
public int legCounts;
/**
* 构造器
*/
public Dog(int legCounts){
this.legCounts = legCounts;
}
/**
* 改变狗腿数目
*/
public void changeLegCounts(){
this.legCounts *= 2;
}
/**
* 将整型变量转换成字符串的方法
*/
public String toString(){
return Integer.toString(this.legCounts);
}
/**
* Object提供的克隆方法
*/
public Object clone(){
//定义一个狗类对象
Dog o = null;
try {
o = (Dog)super.clone();
} catch (Exception e) {
e.printStackTrace();
}
return o;
}
}
/**
* 实现了克隆接口的狗类
*/
public class DogClone implements Cloneable{
//狗腿数
public int legCounts;
//创建一个狗类,传入参数4
public Dog dog = new Dog(4);
/**
* 克隆的方法
*/
public Object clone(){
//定义一个DogClone对象
DogClone o = null;
try {
o = (DogClone)super.clone();
} catch (Exception e) {
e.printStackTrace();
}
//克隆一个狗类对象
o.dog = (Dog)dog.clone();
return o;
}
}
/**
* 在客户端进行调用的类
*/
public class CloneMain {
/**
* 主方法
*/
public static void main(String[] args) {
//创建一个克隆狗对象
DogClone dogClone = new DogClone();
//设置克隆狗的狗腿数
dogClone.legCounts = 3;
System.out.println("原来的克隆狗腿数量:"+dogClone.legCounts);
System.out.println("原来的普通狗腿数量:"+dogClone.dog.legCounts);
//克隆一个克隆狗对象
DogClone dogClone1 = (DogClone)dogClone.clone();
//设置克隆的克隆狗腿数量
dogClone1.legCounts = 2;
//创建一个狗对象
Dog dog = dogClone1.dog;
//改变狗腿数量
dog.changeLegCounts();
System.out.println("克隆后原来的克隆狗腿数量:"+dogClone.legCounts);
System.out.println("克隆后原来的普通狗狗腿数量:"+dogClone.dog.legCounts);
System.out.println("克隆狗的克隆狗狗腿数量:"+dogClone1.legCounts);
System.out.println("克隆后的普通狗的狗腿数量:"+dogClone1.dog.legCounts);
}
}
在Eclipse中的运行结果是:
原来的克隆狗腿数量:3
原来的普通狗腿数量:4
克隆后原来的克隆狗腿数量:3
克隆后原来的普通狗狗腿数量:4
克隆狗的克隆狗狗腿数量:2
克隆后的普通狗的狗腿数量:8
这样就好了,克隆后原来的普通狗和克隆后的普通狗指向了不同的对象。
关于深度克隆的几点说明
①所谓的深度克隆是指让已实现Cloneable接口和复写clone()方法的类中的类对象的类也实现Cloneable接口并复写clone()方法。
②基本数据类型能够自动实现深度克隆。
③由于StringBuffer是一个finanl类,所以不能自动实现深度克隆。
原型模式的应用场景
当创建对象时,使用者不需要知道对象是如何创建的,只需要复制一个已有对象,然后在其上面进行修改以得到自己想要的对象,这个时候可以采用原型模式。
原型模式的优缺点
①优点:在原型模式中,可以动态地添加产品类,而且对整体结构没有影响。
②缺点:由于原型模式需要给每个类都配备一个克隆的方法,这就需要在设计时通盘考虑,因为在已有的类的基础上添加clone操作时比较困难的;而且实现深层次的复制时需要编写一定量的代码。
总之,一句话,要清楚原型模式什么时候用,怎么用,这才是最重要的。