您的位置:首页 > 编程语言 > Java开发

Java设计模式之原型模式

2016-08-26 00:07 274 查看

Java设计模式之原型模式

原型模式的介绍:

 

  原型模式是一个创建型的模式,原型二字表名了该模式应该有一个样板实例,然后用户想从这个样板中复制出一个内部属性一致的对象,这种过程也就是我们所说的“克隆”。被复制的实例就是我们所称的“原型”,对于原型模式来说,他是可以定制的,你先要拷贝什么样的数据就自己定义需要什么样的数据即可。原型模式多用于创建复杂的或者构造耗时的实例,因为在这中情况下,复制一个已经存在的实例可以使得程序的运行效率更高。

  

原型模式的定义: 

  用原型实例指定创建对象的种类,并通过拷贝这些原型创建新的对象。

浅拷贝和深拷贝:

  在原型模式中,我们通常又分为两种拷贝形式,一种是浅拷贝,一种是深拷贝,什么是浅拷贝呢?有时候我们也叫做影子拷贝,因为这份拷贝实际上并不是将原始的样板的所有字段都重新构造了一份,而是副本的字段引用了样板的字段,也就是说在对于引用的拷贝时,原型拷贝中的所有对象属性的引用地址和样板中的对象属性地址是指向同一个地址的。如下图所示:

  


  

  那什么是深拷贝呢?其实就是将对象引用再拷贝一份,让原型拷贝中的对象属性地址和样板中的对象属性地址不一样。具体的介绍请参考实例代码:

原型模式的使用场景:

  

  4.1类初始化需要消耗非常多的资源,这个资源包括数据,硬件资源等,通过原型拷贝避免了这些消耗

  

  4.2通过new产生一个对象需要非常繁琐的数据准备或访问权限,这时可以使用原型模式。

  

  4.3一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值时,可以考虑使用原型模式拷贝多个对象供调用者使用,即保护性拷贝。

  

  需要注意的是,通过实现Cloneable接口的原型模式在调用clone方法构造实例时并不一定比new操作速度快,只有当通过new构造对象较为耗时时或者说成本比较高时,通过clone方法才能获得效率上的提升。因此,在使用Cloneable时需要考虑构建对象的成本以及做一些效率上的测试。当然,实现原型模式也不一定非要实现Cloneable接口,也有其他的实现方式。

原型模式的UML类图



原型模式的简单实现:

  假如我们有一份文档,文员花费了很多时间做好了这个文档,然后根据其他用户的反应,他觉得这份文档可能还有某些地方不足,打算对这个文档做进一步的编辑,但是最后项目经理采纳谁编辑的文档都还不确定,因此,为了安全起见,所有想对这份文档进行下一步修改的,就拷贝一份副本去进行修改。代码如下:

Document.java

/**
* 样板文档,扮演的是ConcretePrototype角色,而cloneable是代表prototype角色
*/
public class Document implements Cloneable{
//文档的标题
private String title;
//文档中的图片
private List<String> image;

public Document(){
image = new ArrayList<>();
}

public String getTitle() {
return title;
}

public void setTitle(String title) {
this.title = title;
}

public List<String> getImage() {
return image;
}

public void setImage(List<String> image) {
this.image = image;
}
public void addImage(String str){
image.add(str);
}

/**
* 打印文档内容
*/
public void display(){
System.out.println("document-title = " + title);
for (String s : image) {
System.out.println("image = " + s);
}
}

/**
* 重写Object中的clone方法
* @return 返回克隆的的对象
* @throws CloneNotSupportedException 如果没有实现Cloneable接口则重写clone方法会抛出该异常
*/
@Override
protected Document clone() throws CloneNotSupportedException {
Document doc = (Document) super.clone();
doc.title = this.title;
doc.image = this.image;
return doc;
}
}


通过上面的这个Document类模式了一个文档中的基本元素,即标题和图片内容,Document在该原型模式实例中扮演的是ConcretePrototype角色,而Cloneable扮演的角色则是Prototype。该对象中的clone方法是用来实现对象克隆的,注意的是这个方法不是Clonealbe接口中的,而是Object中的方法,Cloneable只是一个标志接口,它表名该类的对象是可以拷贝的,如果没有实现Cloneable接口,却调用了clone方法这会抛出如上的异常。下面看看测试类。

Test.java

public class Test {
public static void main(String[] args) {
//先创建一个样板对象
Document doc1 = new Document();
//为样板编辑标题和图片
doc1.setTitle("样版文档标题");
doc1.addImage("图片1");
doc1.addImage("图片2");
doc1.addImage("图片3");
//显示样板内容
System.out.println("样板内容");
doc1.display();

try {
//克隆样板对象给doc2
Document doc2 = doc1.clone();
//打印模板内容
System.out.println("未修改的模板文档内容");
doc2.display();
//修改模板的标题
doc2.setTitle("修改后的文档标题");
//打印模板内容
System.out.println("修改后的模板内容");
doc2.display();
//打印样板内容
System.out.println("样板内容");
doc1.display();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}

}
}


运行结果如下:

**************构造函数**************
样板内容
document-title = 样版文档标题
image = 图片1
image = 图片2
image = 图片3
****************************
未修改的模板文档内容
document-title = 样版文档标题
image = 图片1
image = 图片2
image = 图片3
****************************
修改后的模板内容
document-title = 修改后的文档标题
image = 图片1
image = 图片2
image = 图片3
****************************
样板内容
document-title = 样版文档标题
image = 图片1
image = 图片2
image = 图片3
****************************


从上面可以看到,doc2是通过doc1.clone创建的,并且在没有修改模板内容的情况下输出结果和doc1一样,所以可以看出来doc2是拷贝了doc1,因为他们内容一样,当doc2文本内容后并不会影响doc1的文本内容,这就保证了doc1的安全性。还需要注意的是,通过clone拷贝对象时并不会执行构造函数,因此,如果在构造函数中需要一些特殊的初始化操作类型,在使用Cloneable实现拷贝时,需要注意构造函数不会执行的问题。然后我们再看下一个测试类:

Test.java

public class Test {
public static void main(String[] args) {
//先创建一个样板对象
Document doc1 = new Document();
//为样板编辑标题和图片
doc1.setTitle("样版文档标题");
doc1.addImage("图片1");
doc1.addImage("图片2");
doc1.addImage("图片3");
//显示样板内容
System.out.println("样板内容");
doc1.display();

try {
//克隆样板对象给doc2
Document doc2 = doc1.clone();
//打印模板内容
System.out.println("未修改的模板文档内容");
doc2.display();
//修改模板的标题
doc2.setTitle("修改后的文档标题");
//打印模板内容
System.out.println("修改后的模板内容");
doc2.addImage("图片5");
doc2.addImage("图片6");
doc2.display();
//打印样板内容
System.out.println("样板内容");
doc1.display();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}

}
}


再看看输出结果:

**************构造函数**************
样板内容
document-title = 样版文档标题
image = 图片1
image = 图片2
image = 图片3
****************************
未修改的模板文档内容
document-title = 样版文档标题
image = 图片1
image = 图片2
image = 图片3
****************************
修改后的模板内容
document-title = 修改后的文档标题
image = 图片1
image = 图片2
image = 图片3
image = 图片5
image = 图片6
****************************
样板内容
document-title = 样版文档标题
image = 图片1
image = 图片2
image = 图片3
image = 图片5
image = 图片6
****************************


相信细心的同学已经看到上面运行结果的区别了,那就是我们在doc2中添加了“图片5”和“图片6”,输出的时候在doc1中也显示了,那么这是什么回事呢?其实这就是我们前面所说的浅拷贝了,因为在浅拷贝中,doc1中的image集合对象的引用,和doc2中的image集合对象的引用是指向同一个的,在clone方法中只是简单的将doc1的this.image赋值给了doc2的image。所以在修改doc2中的内容的时候,导致了doc1中的内容也发生了改变,当然有些童鞋会觉得doc1中的String title不也是一个对象吗,为什么doc2修改标题的时候没有让doc1的标题也改变呢?关于这个问题,那就说明你对String类型的使用不太了解了,那么我只简单提示一句,具体的就不详细讲解,那就是String是个常量类,其值是不能修改的,但是可以让该对象指向另一个字符串地址。比如,String a = “abcde”; String b = “abc” ; a = b; 对于a = b这条语句,我们不是将b的值赋值给a,而是将a指向b所指向的地址。所以对于我们这个问题来说,只有image对应拷贝的引用是相同的,则所有指向该地址的所有对象,一改全改。对于上面这个问题你是不是觉得既然修改doc2也修改了doc1,那不就是不符合我们的题意么,说好的修改副本,样板也跟着改了,那么有什么办法能让我们不管什么情况下修改doc2都不会改变doc1的值吗?答案就是使用深拷贝了。看如下代码,只需要修改doc1对象中的clone方法代码:

/**
* 重写Object中的clone方法
* @return 返回克隆的的对象
* @throws CloneNotSupportedException 如果没有实现Cloneable接口则重写clone方法会抛出该异常
*/
@Override
protected Document clone() throws CloneNotSupportedException {
Document doc = (Document) super.clone();
doc.title = this.title;
//只要将对象单独再拷贝一份,则可以达到我们预期的效果了
doc.image = (List<String>) ((ArrayList<String>)this.image).clone();
return doc;
}


运行结果如下:

**************构造函数**************
样板内容
document-title = 样版文档标题
image = 图片1
image = 图片2
image = 图片3
****************************
未修改的模板文档内容
document-title = 样版文档标题
image = 图片1
image = 图片2
image = 图片3
****************************
修改后的模板内容
document-title = 修改后的文档标题
image = 图片1
image = 图片2
image = 图片3
image = 图片5
image = 图片6
****************************
样板内容
document-title = 样版文档标题
image = 图片1
image = 图片2
image = 图片3
****************************


可以发现,通过将doc.image 指向 this.image的一份拷贝,而不是this.image本身,这样的doc1中的内容并不受doc2修改的影响了。

其实原型模式是非常简单的一个模式,它的核心问题就是对原始对象进行拷贝,在这个模式的使用过程中需要注意的一点就是:深、浅拷贝的问题。对于初学在来说,在使用该模式的时候,如果不熟练的情况下,建议都使用深拷贝,这样就能避免副本操作时影响样板的问题了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: