首页 > 代码库 > 深入浅出设计模式 ------ Prototype(原型模式)之深度克隆

深入浅出设计模式 ------ Prototype(原型模式)之深度克隆

  继上篇深入浅出设计模式 ------ Prototype(原型模式)的浅克隆实现, 本文进入Prototype(原型模式)的进阶篇----深度克隆。



深度克隆 ---- 序列化方式实现

  把对象写到流里的过程是序列化(Serilization)过程,而把对象从流中读出来的过程则叫做反序列化(Deserialization)。写在流里的是对象的一个克隆(新的, 独立的), 而原对象仍存在于JVM内存模型里。因此, 以下代码采用序列化方式实现深度克隆。


第一步: 将上篇的代码做些许改动, 加入对象引用(以便测试浅克隆和深克隆的区别)。


原型接口: FruitPrototype保持不变

package com.wenniuwuren.prototype;
/**
 * 原型接口
 * @author wenniuwuren
 *
 */
public interface FruitPrototype{  
     public abstract FruitPrototype shallowClone() throws CloneNotSupportedException;  
}  
  


原型具体实现 : 加入引用以及属性的getter和setter方法方便测试比较

package com.wenniuwuren.prototype;
/**
 * 原型具体实现
 * @author wenniuwuren
 *
 */

public class ConcteteFruitPrototype implements FruitPrototype, Cloneable{  
    private String size;  
    private String color;  
	private Vitamins vitamins;
    
    public ConcteteFruitPrototype(String size, String color, Vitamins vitamins) {  
        this.size = size;  
        this.color = color;  
        this.vitamins = vitamins;
    }  
    
    // 克隆
    public FruitPrototype shallowClone() throws CloneNotSupportedException  {  
        return (FruitPrototype) super.clone();  
    } 
    
    // 方便打印
    public void display(String colorname) {  
        System.out.println(colorname+"的大小是: "+size+" 颜色是:"+color);  
    }

    
	public Vitamins getVitamins() {
		return vitamins;
	}

	public void setVitamins(Vitamins vitamins) {
		this.vitamins = vitamins;
	}  
	
	public String getSize() {
		return size;
	}

	public void setSize(String size) {
		this.size = size;
	}

	public String getColor() {
		return color;
	}

	public void setColor(String color) {
		this.color = color;
	}
}  
  


原型管理类: FruitTool 保持不变

package com.wenniuwuren.prototype;

import java.util.HashMap;
/**
 * 原型管理类
 * @author wenniuwuren
 *
 */
public class FruitTool {
	private HashMap<String, FruitPrototype> fruits = new HashMap<String, FruitPrototype>();

	public void put(String key, FruitPrototype fruitPrototype) {
		fruits.put(key, fruitPrototype);
	}

	public FruitPrototype get(String key) {
		return fruits.get(key);
	}
}


水果都含有的维生素类:Vitamins为新增的对象引用类, 仅提供一个属性方便测试。

package com.wenniuwuren.prototype;
/**
 * 水果都含有的维生素类
 * @author wenniuwuren
 *
 */
public class Vitamins {
    
	private Boolean isContainsVitaminA;

	public Boolean getIsContainsVitaminA() {
		return isContainsVitaminA;
	}

	public void setIsContainsVitaminA(Boolean isContainsVitaminA) {
		this.isContainsVitaminA = isContainsVitaminA;
	}

}


测试类:

package com.wenniuwuren.prototype;

import java.io.IOException;

public class Client {
	public static void main(String[] args) throws CloneNotSupportedException, ClassNotFoundException, IOException {
		
		FruitTool fruitTool = new FruitTool();

		// 初始化水果的大小和颜色
		fruitTool.put("Apple", new ConcteteFruitPrototype("Middle", "Green", new Vitamins()));
		fruitTool.put("Watermelon", new ConcteteFruitPrototype("Large", "Red", new Vitamins()));
		fruitTool.put("Lemon", new ConcteteFruitPrototype("Small", "Yellow", new Vitamins()));

		
		System.out.println("#######################浅克隆测试########################");
		String fruitName = "Apple";
		ConcteteFruitPrototype concteteFruitPrototype1 = (ConcteteFruitPrototype) fruitTool
				.get(fruitName).shallowClone();
		concteteFruitPrototype1.display(fruitName);
		
		System.out.print("赋值前, 浅克隆后和浅克隆前的String类型数据相等:"  );
		System.out.println(concteteFruitPrototype1.getColor().equals(((ConcteteFruitPrototype)fruitTool
				.get(fruitName)).getColor()));
		
		System.out.print("赋值前, 浅克隆后和浅克隆前的 对象引用相等:"  );
		System.out.println(concteteFruitPrototype1.getVitamins().getIsContainsVitaminA() == ((ConcteteFruitPrototype)fruitTool
		.get(fruitName)).getVitamins().getIsContainsVitaminA());
		
		System.out.println("----------------分别对克隆后对象赋值, 观察数据是否独立--------------------");
	
		concteteFruitPrototype1.setColor("Red");
		System.out.print("赋值后,浅克隆后和浅克隆前的String类型数据相等:");
		System.out.println(concteteFruitPrototype1.getColor().equals(((ConcteteFruitPrototype)fruitTool
				.get(fruitName)).getColor()));
		
		concteteFruitPrototype1.getVitamins().setIsContainsVitaminA(Boolean.FALSE);
		System.out.print("赋值后, 浅克隆后和浅克隆前的 对象引用相等:"  );
		System.out.println(concteteFruitPrototype1.getVitamins().getIsContainsVitaminA() == ((ConcteteFruitPrototype)fruitTool
		.get(fruitName)).getVitamins().getIsContainsVitaminA());
		
	}
}


测试结果:

Apple的大小是: Middle 颜色是:Green
---------我是邪恶的分割线------------------
赋值前, 浅克隆后和浅克隆前的String类型数据相等:true
赋值前, 浅克隆后和浅克隆前的 对象引用相等:true
----------------分别对克隆后对象赋值, 观察数据是否独立--------------------
赋值后,浅克隆后和浅克隆前的String类型数据相等:false
赋值后, 浅克隆后和浅克隆前的 对象引用相等:true
可以看出浅克隆确实如上篇所提到的只负责克隆按值传递的数据(String类型的数据确实是克隆过去了, 已经和旧的String数据独立开了),而不复制它所引用的对象(对象引用还是只是保存在JVM内存模型中的同一份, 其中一个引用数据更改后, 引用数据还是保持一致)。



第二步 :加入深度克隆

原型接口 : 添加深度克隆方法

package com.wenniuwuren.prototype;

import java.io.IOException;

/**
 * 原型接口
 * 
 * @author wenniuwuren
 *
 */
public interface FruitPrototype {
	// 浅度克隆
	public abstract FruitPrototype shallowClone()
			throws CloneNotSupportedException;

	// 深度克隆
	public FruitPrototype deepClone() throws IOException, ClassNotFoundException;
}


原型具体实现 : 深度克隆实现

package com.wenniuwuren.prototype;

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

/**
 * 原型具体实现
 * @author wenniuwuren
 *
 */
//深度克隆--实现序列化不可少, 作为传输数据的标识
public class ConcteteFruitPrototype implements FruitPrototype, Cloneable, Serializable{  
	
	private static final long serialVersionUID = 508114335347191627L;
	
	private String size;  
    private String color;  
	private Vitamins vitamins;
    
    public ConcteteFruitPrototype(String size, String color, Vitamins vitamins) {  
        this.size = size;  
        this.color = color;  
        this.vitamins = vitamins;
    }  
    
    // 浅克隆
    public FruitPrototype shallowClone() throws CloneNotSupportedException  {  
        return (FruitPrototype) super.clone();  
    } 
    
    // 深克隆
    @Override
	public FruitPrototype deepClone() throws IOException, ClassNotFoundException {
    	//将对象序列化到流里
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);
        oos.writeObject(this);
        //将流反序列化回来
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bis);
        return (FruitPrototype)ois.readObject();
	}
    
    // 方便打印
    public void display(String colorname) {  
        System.out.println(colorname+"的大小是: "+size+" 颜色是:"+color);  
    }

    
	public Vitamins getVitamins() {
		return vitamins;
	}

	public void setVitamins(Vitamins vitamins) {
		this.vitamins = vitamins;
	}  
	
	public String getSize() {
		return size;
	}

	public void setSize(String size) {
		this.size = size;
	}

	public String getColor() {
		return color;
	}

	public void setColor(String color) {
		this.color = color;
	}

	
}  
  


原型管理类 : 保持不变

package com.wenniuwuren.prototype;

import java.util.HashMap;
/**
 * 原型管理类
 * @author wenniuwuren
 *
 */
public class FruitTool {
	private HashMap<String, FruitPrototype> fruits = new HashMap<String, FruitPrototype>();

	public void put(String key, FruitPrototype fruitPrototype) {
		fruits.put(key, fruitPrototype);
	}

	public FruitPrototype get(String key) {
		return fruits.get(key);
	}
}


维生素类 : 注意实现序列化

package com.wenniuwuren.prototype;

import java.io.Serializable;

/**
 * 水果都含有的维生素类
 * @author wenniuwuren
 *
 */
// 深度克隆--实现序列化不可少, 作为传输数据的标识
public class Vitamins implements Serializable{
    
	private static final long serialVersionUID = 1183447243521084226L;
	
	private Boolean isContainsVitaminA;

	public Boolean getIsContainsVitaminA() {
		return isContainsVitaminA;
	}

	public void setIsContainsVitaminA(Boolean isContainsVitaminA) {
		this.isContainsVitaminA = isContainsVitaminA;
	}

}


测试类 : 

package com.wenniuwuren.prototype;

import java.io.IOException;

public class Client {
	public static void main(String[] args) throws CloneNotSupportedException, ClassNotFoundException, IOException {
		
		FruitTool fruitTool = new FruitTool();

		// 初始化水果的大小和颜色
		fruitTool.put("Apple", new ConcteteFruitPrototype("Middle", "Green", new Vitamins()));
		fruitTool.put("Watermelon", new ConcteteFruitPrototype("Large", "Red", new Vitamins()));
		fruitTool.put("Lemon", new ConcteteFruitPrototype("Small", "Yellow", new Vitamins()));

		
		System.out.println("#######################浅克隆测试########################");
		String fruitName = "Apple";
		ConcteteFruitPrototype concteteFruitPrototype1 = (ConcteteFruitPrototype) fruitTool
				.get(fruitName).shallowClone();
		concteteFruitPrototype1.display(fruitName);
		
		System.out.print("赋值前, 浅克隆后和浅克隆前的String类型数据相等:"  );
		System.out.println(concteteFruitPrototype1.getColor().equals(((ConcteteFruitPrototype)fruitTool
				.get(fruitName)).getColor()));
		
		System.out.print("赋值前, 浅克隆后和浅克隆前的 对象引用相等:"  );
		System.out.println(concteteFruitPrototype1.getVitamins().getIsContainsVitaminA() == ((ConcteteFruitPrototype)fruitTool
		.get(fruitName)).getVitamins().getIsContainsVitaminA());
		
		System.out.println("----------------分别对克隆后对象赋值, 观察数据是否独立--------------------");
	
		concteteFruitPrototype1.setColor("Red");
		System.out.print("赋值后,浅克隆后和浅克隆前的String类型数据相等:");
		System.out.println(concteteFruitPrototype1.getColor().equals(((ConcteteFruitPrototype)fruitTool
				.get(fruitName)).getColor()));
		
		concteteFruitPrototype1.getVitamins().setIsContainsVitaminA(Boolean.FALSE);
		System.out.print("赋值后, 浅克隆后和浅克隆前的 对象引用相等:"  );
		System.out.println(concteteFruitPrototype1.getVitamins().getIsContainsVitaminA() == ((ConcteteFruitPrototype)fruitTool
		.get(fruitName)).getVitamins().getIsContainsVitaminA());
		
		System.out.println("#######################深克隆测试########################");
		fruitName = "Watermelon";
		ConcteteFruitPrototype concteteFruitPrototype2 = (ConcteteFruitPrototype) fruitTool
				.get(fruitName).deepClone();
		concteteFruitPrototype2.display(fruitName);
		
		System.out.print("赋值前, 深克隆后和深克隆前的String类型数据相等:"  );
		System.out.println(concteteFruitPrototype2.getColor().equals(((ConcteteFruitPrototype)fruitTool
				.get(fruitName)).getColor()));
		
		System.out.print("赋值前, 深克隆后和深克隆前的 对象引用相等:"  );
		System.out.println(concteteFruitPrototype2.getVitamins().getIsContainsVitaminA() == ((ConcteteFruitPrototype)fruitTool
		.get(fruitName)).getVitamins().getIsContainsVitaminA());
		
		System.out.println("----------------分别对克隆后对象赋值, 观察数据是否独立--------------------");
	
		concteteFruitPrototype2.setColor("Yellow");
		System.out.print("赋值后,深克隆后和深克隆前的String类型数据相等:");
		System.out.println(concteteFruitPrototype2.getColor().equals(((ConcteteFruitPrototype)fruitTool
				.get(fruitName)).getColor()));
		
		concteteFruitPrototype2.getVitamins().setIsContainsVitaminA(Boolean.FALSE);
		System.out.print("赋值后, 深克隆后和深克隆前的 对象引用相等:"  );
		System.out.println(concteteFruitPrototype2.getVitamins().getIsContainsVitaminA() == ((ConcteteFruitPrototype)fruitTool
		.get(fruitName)).getVitamins().getIsContainsVitaminA());
	}
}


测试结果 : 

#######################浅克隆测试########################
Apple的大小是: Middle 颜色是:Green
赋值前, 浅克隆后和浅克隆前的String类型数据相等:true
赋值前, 浅克隆后和浅克隆前的 对象引用相等:true
----------------分别对克隆后对象赋值, 观察数据是否独立--------------------
赋值后,浅克隆后和浅克隆前的String类型数据相等:false
赋值后, 浅克隆后和浅克隆前的 对象引用相等:true
#######################深克隆测试########################
Watermelon的大小是: Large 颜色是:Red
赋值前, 深克隆后和深克隆前的String类型数据相等:true
赋值前, 深克隆后和深克隆前的 对象引用相等:true
----------------分别对克隆后对象赋值, 观察数据是否独立--------------------
赋值后,深克隆后和深克隆前的String类型数据相等:false
赋值后, 深克隆后和深克隆前的 对象引用相等:false

可以从测试结果清楚看出, 深度克隆与浅度克隆的最大区别便是深度克隆把对象引用一起克隆了一份。 所以结果中修改引用内数据将导致引用数据前后不一致。


至此, 深入浅出设计模式系列的创建型模式就结束了, 接下来年前工作繁忙, 要改一个开源代码, 时间不多, 尽量挤时间写其他的模式, 希望博友们多提意见, 有助于笔者提高博客质量。 (晚安, 我爱这个世界)


深入浅出设计模式 ------ Prototype(原型模式)之深度克隆