创建型(二)—原型模式

1、介绍

Specify the kinds of Objects to create using a prototypical instance, and create new objects by copying this prototype. 

意思是:用原型实例指定创建对象的种类,并且通过复制这些原型创建新的对象。

2、使用场景

  1. 类的初始化需要消耗很多资源,包括数据、硬件等,通过原型拷贝可以避免这种消耗
  2. 通过new 产生一个对象需要繁琐的数据准备或访问权限,这时也使用原型模式
  3. 一个对象需要提供给其他对象访问,而且各个调用者可能都需要修改其值,可以考虑使用原型模型拷贝多个对象供调用者使用,即保护性拷贝。

    在实际项目中,原型模式很少单独出现,一般是和工厂方法模式一起出现,通过clone()方法创建一个对象,然后由工厂提供给调用者。

    注意

  1. Java中Object提供的clone()方法时浅拷贝,只复制关联对象的引用,而不复制关联对象的数据。
  2. 通过Cloneable接口实现的原型模式在调用clone()构造实例时不一定比通过new方法快,只有当通过new构造对象耗时耗资源时,clone()方法才会体现效率有所提升。

3、UML类图

角色介绍:

  1. Client:提出创建对象的请求
  2. Prototype:抽象类或接口,声明具体clone能力
  3. ConcretePrototype:具体的原型类,被复制的对象,实现抽象原型接口

4、示例

下面以文档拷贝为例来演示,首先创建一个文档对象WordDocument, 包含文字和图片。用户经过长时间编辑后,打算对文档做进一步编辑,但是不确定编辑的后文档是否被采用。因此,为了安全起见,将当前文档拷贝一份,在副本上进行修改。

import java.util.ArrayList;

public class WordDocument implements Cloneable{
	private String mText;
	private ArrayList<String> mImages = new ArrayList<>();
	public WordDocument () {
		System.out.println("----WordDocument构造函数----");
	}
	
	@Override
	protected WordDocument clone() {
		try {
			WordDocument document = (WordDocument) super.clone();
			document.mImages = this.mImages;
			document.mText = this.mText;
			return document;
			
		} catch (Exception e) {
		}
		return null;
	}

	public String getmText() {
		return mText;
	}

	public void setmText(String mText) {
		this.mText = mText;
	}

	public ArrayList<String> getmImages() {
		return mImages;
	}

	public void setmImages(ArrayList<String> mImages) {
		this.mImages = mImages;
	}
	
	public void addImage(String image) {
		this.mImages.add(image);
	}
	
	public void showWordDocument() {
		System.out.println("----showDocument--start--");
		System.out.println("Text : " + mText);
		for(String img : mImages) {
			System.out.println("image name : " + img);
		}
		System.out.println("----showDocument--end--");
	}
}

WordDocument 充当ConcretePrototype角色,Cloneable则为Prototype角色。注意:clone()并不是Cloneable接口中的,而是Object中的方法。Cloneable是一个标识接口,表示这个实现类对象时可拷贝的。若是没有实现Cloneable,而调用了clone方法将会抛出异常。

下面演示Client端的使用:

public class Client {
	public static void main(String[] args) {
		WordDocument originDoc = new WordDocument();
		originDoc.setmText("文档");
		originDoc.addImage("图片1");
		originDoc.addImage("图片2");
	
		WordDocument copyDoc = (WordDocument) originDoc.clone();
		copyDoc.setmText("修改后文档");
		System.out.println("----展示修改后的copyDoc内容----");
		copyDoc.showWordDocument();
		System.out.println("----展示原始originDoc内容----");
		originDoc.showWordDocument();
	}
}

输出结果如下:

----WordDocument构造函数----
----展示修改后的copyDoc内容----
----showDocument--start--
Text : 修改后文档
image name : 图片1
image name : 图片2
----showDocument--end--
----展示原始originDoc内容----
----showDocument--start--
Text : 文档
image name : 图片1
image name : 图片2
----showDocument--end--

copyDoc是通过originDoc.clone()创建的,copyDoc修改了文本内容不会影响originDoc的文本内容,这保证了originDoc的安全性。注意:通过clone拷贝对象不会执行构造函数。因此,若在构造函数中需要一些特殊的初始化操作,在使用Cloneable实现拷贝是,需要注意构造函数不会执行的问题。

浅拷贝与深拷贝

上述原型模式实际上是一个浅拷贝,也称影子拷贝。并不是将原始文档的所有字段都重新构造了一份,而是副本文档的字段引用原始文档的字段。

public class Client {
	public static void main(String[] args) {
		WordDocument originDoc = new WordDocument();
		originDoc.setmText("文档");
		originDoc.addImage("图片1");
		originDoc.addImage("图片2");
	
		WordDocument copyDoc = (WordDocument) originDoc.clone();
		copyDoc.setmText("修改后文档");
		copyDoc.addImage("修改图片");
		System.out.println("----展示修改后的copyDoc内容----");
		copyDoc.showWordDocument();
		System.out.println("----展示原始originDoc内容----");
		originDoc.showWordDocument();
	}
}

输出结果如下:

----WordDocument构造函数----
----展示修改后的copyDoc内容----
----showDocument--start--
Text : 修改后文档
image name : 图片1
image name : 图片2
image name : 修改图片
----showDocument--end--
----展示原始originDoc内容----
----showDocument--start--
Text : 文档
image name : 图片1
image name : 图片2
image name : 修改图片
----showDocument--end--

从结果中可以看出,最后两个文档的信息输出是一致的。在copyDoc中添加了“修改图片”,originDoc中也会随之更改。因为clone只是浅拷贝,copyDoc中的mImages仅仅指向originDoc的mImage的引用,并没有重新构造一个新的对象,copyDoc与originDoc都是指向同一个对象。深拷贝可以解决这个问题,有两种方式解决:

   1) 继续利用clone方法,对其内的引用类型变量再进行clone()。

@Override
	protected WordDocument clone() {
		try {
			WordDocument document = (WordDocument) super.clone();
			document.mImages = (ArrayList<String>) this.mImages.clone();
			document.mText = this.mText;
			return document;
			
		} catch (Exception e) {
		}
		return null;
	}

    2) 序列化(serialization)这个对象,再反序列化回来,就可以得到这个新的对象,无非就是序列化的规则需要我们自己来写。

import java.io.Serializable;
import java.util.ArrayList;

public class WordDocument implements Serializable{
	private String mText;
	private ArrayList<String> mImages = new ArrayList<>();
	public WordDocument () {
		System.out.println("----WordDocument构造函数----");
	}
	public String getmText() {
		return mText;
	}

	public void setmText(String mText) {
		this.mText = mText;
	}

	public ArrayList<String> getmImages() {
		return mImages;
	}

	public void setmImages(ArrayList<String> mImages) {
		this.mImages = mImages;
	}
	
	public void addImage(String image) {
		this.mImages.add(image);
	}
	
	public void showWordDocument() {
		System.out.println("----showDocument--start--");
		System.out.println("Text : " + mText);
		for(String img : mImages) {
			System.out.println("image name : " + img);
		}
		System.out.println("----showDocument--end--");
	}
}

WordDocument需要序列号,实现Serializable。Client端代码如下:

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

public class Client {
	public static void main(String[] args) {
		try {
			WordDocument originDoc = new WordDocument();
			originDoc.setmText("文档");
			originDoc.addImage("图片1");
			originDoc.addImage("图片2");
			ByteArrayOutputStream bos=new ByteArrayOutputStream();
	        ObjectOutputStream oos = new ObjectOutputStream(bos);
			oos.writeObject(originDoc);
	        oos.flush();
	        ObjectInputStream ois=new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
	        WordDocument copyDoc = (WordDocument)ois.readObject();
	        copyDoc.setmText("修改后文档");
			copyDoc.addImage("修改图片");
			System.out.println("----展示修改后的copyDoc内容----");
			copyDoc.showWordDocument();
			System.out.println("----展示原始originDoc内容----");
			originDoc.showWordDocument();
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

上述两种方法一样,输出结果如下:

----WordDocument构造函数----
----展示修改后的copyDoc内容----
----showDocument--start--
Text : 修改后文档
image name : 图片1
image name : 图片2
image name : 修改图片
----showDocument--end--
----展示原始originDoc内容----
----showDocument--start--
Text : 文档
image name : 图片1
image name : 图片2
----showDocument--end--

 copyDoc指向的是originDoc.mImages的一份拷贝,所以在copyDoc中添加图片并不会影响原始数据。原型模式的核心问题就是对原始对象进行拷贝,在开发过程中,需要根据具体的使用场景,斟酌使用深拷贝和浅拷贝。

5、总结

优点:

原型模式是内存二进制流的复制,要比直接new 一个对象性能好,特别是在一个循环体内产生大量的对象时,原型模式可以更好地体现其优点。

缺点:

直接在内存中复制,构造函数是不会执行的,虽然减少了约束,在实际开发中需注意这个潜在问题。

猜你喜欢

转载自blog.csdn.net/zcjxaiofeizhu/article/details/80640060