(原创)认识设计模式-原型

2015/03/08 设计模式

前提

原型模式其实大家早就知道了,只是不知道这个专业术语而已,为什么这么说呢?西游记大家都看过吧,里面的孙悟空把几根毫毛一吹,就出来几只和孙悟空一模一样的猴子,这就是典型的原型模式。

介绍

img

上图为原型模式的UML图,有些资料把新克隆出来的对象画成是原对象的子类,我个人觉得不对,其实他们是同一层级的关系,因此在这图里我个人做了些改动。

原型模式很好理解,注意有一个Clone方法就行,恰好在JAVA语言中,已经融合了原型模式,自带了Cloneable接口,因此我们在需要使用原型模式的时候实现Cloneable接口,并完成克隆操作即可。

使用方式

使用原型模式有两点需要注意:

  • 区分深拷贝和浅拷贝的不同
  • 认识到拷贝过程不会触发构造函数

浅拷贝模式

public class Prototype implements Cloneable{

	private String mName;
	
	@Override
	protected Object clone() throws CloneNotSupportedException {

		return super.clone();
	}

	public String getName() {
		return mName;
	}

	public void setName(String name) {
		this.mName = name;
	}

}

public class Main {

	public static void main(String[] args) throws Exception {

		Prototype a = new Prototype();
		a.setName("a");
		Prototype b = (Prototype) a.clone();
		
		System.out.println("a.name:"+a.getName()+"-"+a);
		System.out.println("b.name:"+b.getName()+"-"+b);
		
	}
}

结果如下:

a.name:a-applet.Prototype@2a139a55
b.name:a-applet.Prototype@15db9742

我们发现虽然a和b的mName值一样,但对象的引用值却不同,这说明产生了两个对象,而且b对象和a对象的内容一模一样。

我们在上面的例子中加以改进下:

public class Prototype implements Cloneable{
	
	private String mName;
	private List<String> mList= new ArrayList<String>();
	@Override
	protected Object clone() throws CloneNotSupportedException {

		return super.clone();
	}

	public String getName() {
		return mName;
	}

	public void setName(String name) {
		this.mName = name;
	}

	public List<String> getList() {
		return mList;
	}

	public void setList(List<String> list) {
		this.mList = list;
	}

}

public class Main {

	public static void main(String[] args) throws Exception {

		Prototype a = new Prototype();
		a.setName("a");
		a.getList().add("abc");
		Prototype b = (Prototype) a.clone();
		b.setName("b");
		b.getList().add("123");

 		
    System.out.println("a.name:"+a.getName()+"-"+a);
    System.out.println("b.name:"+b.getName()+"-"+b);

    System.out.println("a.list:"+a.getList());
    System.out.println("b.list:"+b.getList());

}
}

结果如下:

a.name:a-applet.Prototype@2a139a55
b.name:b-applet.Prototype@15db9742
a.list:[abc, 123]
b.list:[abc, 123]

是不是很奇怪?怎么a对象也有“123”?其实上面的代码就是浅拷贝带来的问题,在clone()方法中super.clone();仅仅帮你克隆基础数据类型,类似long,int,char,即使String类,java也把它当作基础数据类型,对于数组,列表,类,系统不会克隆,仅仅把引用传给新对象,因此相当于克隆后的对象们共同分享,在特殊场合这就会造成混乱。

深拷贝模式

    public class Prototype implements Cloneable{
    	
    	private String mName;
    	private ArrayList<String> mList= new ArrayList<String>();
    	@Override
    	protected Object clone() throws CloneNotSupportedException {
    		Prototype other =  (Prototype) super.clone();
    		other.mList = (ArrayList<String>) mList.clone();
    		return other;
    	}
    
    	public String getName() {
    		return mName;
    	}
    
    	public void setName(String name) {
    		this.mName = name;
    	}
    
    	public List<String> getList() {
    		return mList;
    	}
    
    	public void setList(ArrayList<String> list) {
    		this.mList = list;
    	}
    
    }
    
    public class Main {
    
    	public static void main(String[] args) throws Exception {
    
    		Prototype a = new Prototype();
    		a.setName("a");
    		a.getList().add("abc");
    		Prototype b = (Prototype) a.clone();
    		b.setName("b");
    		b.getList().add("123");
   		
    		System.out.println("a.name:"+a.getName()+"-"+a);
    		System.out.println("b.name:"+b.getName()+"-"+b);
    		
    		System.out.println("a.list:"+a.getList());
    		System.out.println("b.list:"+b.getList());
    		
    	}
    }

结果如下:

a.name:a-applet.Prototype@2a139a55
b.name:b-applet.Prototype@15db9742
a.list:[abc]
b.list:[abc, 123]

这下就得到我们想要的结果了,既然系统不帮我们做,我们只好在clone()方法中自己再多做一些事情了,^_^

构造函数不触发

我们再修改下代码,如下:

    public class Prototype implements Cloneable{
    	
    	private String mName;
    	private ArrayList<String> mList= new ArrayList<String>();
    	
    	public Prototype()
    	{
    		System.out.println("Prototype()");
    	}
    	@Override
    	protected Object clone() throws CloneNotSupportedException {
    		Prototype other =  (Prototype) super.clone();
    		other.mList = (ArrayList<String>) mList.clone();
    		return other;
    	}
    
    	public String getName() {
    		return mName;
    	}
    
    	public void setName(String name) {
    		this.mName = name;
    	}
    
    	public List<String> getList() {
    		return mList;
    	}
    
    	public void setList(ArrayList<String> list) {
    		this.mList = list;
    	}
    
    }
    
    public class Main {
    
    	public static void main(String[] args) throws Exception {
    
    		Prototype a = new Prototype();
    		a.setName("a");
    		a.getList().add("abc");
    		Prototype b = (Prototype) a.clone();
    		b.setName("b");
    		b.getList().add("123");
    	
    		System.out.println("a.name:"+a.getName()+"-"+a);
    		System.out.println("b.name:"+b.getName()+"-"+b);
    		
    		System.out.println("a.list:"+a.getList());
    		System.out.println("b.list:"+b.getList());
    		
    	}
    }

结果如下:

Prototype()
a.name:a-applet.Prototype@2a139a55
b.name:b-applet.Prototype@15db9742
a.list:[abc]
b.list:[abc, 123]

发现Prototype()只打印了一次,说明在java的原型模式机制中,构造函数不会触发,仅仅是从二进制层面拷贝了一个对象而已,因此在实际运用中,我们应该心里有数,以便遇到bug时知道哪些地方有可能会出问题。



知识共享许可协议
本作品采用知识共享署名-非商业性使用 4.0 国际许可协议进行许可。

站内搜索

    撩我备注-博客

    joinee

    目录结构