深入浅出设计模式 ------ 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,原型模式)