天天看點

設計模式 | 三、原型模式(淺克隆、深克隆)[PrototypePattern]原型模式

原型模式

源碼:https://github.com/GiraffePeng/design-patterns

1、定義

原型模式(Prototype Pattern)是指原型執行個體指定建立對象的種類,并且通過拷貝這些原型建立新的對象。

2、應用場景

原型模式主要适用于以下場景:

  • 1、類初始化消耗資源較多。
  • 2、new 産生的一個對象需要非常繁瑣的過程(資料準備、通路權限等)
  • 3、構造函數比較複雜。
  • 4、循環體中生産大量對象時。

在 Spring 中,原型模式應用得非常廣泛,例如 scope=“prototype”。在我們經常用的 JSON.parseObject()也是一種原型模式。

3、淺克隆

針對接口程式設計,讓我們先建立一個克隆接口PrototypeInteface.java

public interface PrototypeInteface {
	public PrototypeInteface clone();
}
           

建立一個實體類實作PrototypeInteface接口,重寫clone方法。

public class ShallowPrototype implements PrototypeInteface{

	private int id;
	
	private String name;
	
	private String type;
	
	private List<String> arr;
	
	public List<String> getArr() {
		return arr;
	}

	public void setArr(List<String> arr) {
		this.arr = arr;
	}

	public ShallowPrototype() {
		super();
	}

	public ShallowPrototype(int id, String name, String type,List<String> arr) {
		super();
		this.id = id;
		this.name = name;
		this.type = type;
		this.arr = arr;
	}

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

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

	public String getType() {
		return type;
	}

	public void setType(String type) {
		this.type = type;
	}
	
	@Override
	public ShallowPrototype clone() {
		ShallowPrototype shallowPrototype = new ShallowPrototype();
		shallowPrototype.setId(this.id);
		shallowPrototype.setName(this.name);
		shallowPrototype.setType(this.type);
		shallowPrototype.setArr(this.arr);
		return shallowPrototype;
	}
}
           

建立測試類:

public class PrototypeTest {

	public static void main(String[] args) {
		ShallowPrototype shallowPrototype = new ShallowPrototype(1, "name", "type",new ArrayList<String>() {{
			add("測試");
		}});
		ShallowPrototype clone = shallowPrototype.clone();
		System.out.println(shallowPrototype.getName() == clone.getName());
		System.out.println(shallowPrototype.getArr() == clone.getArr());
		clone.getArr().add("修改");
		List<String> arr = shallowPrototype.getArr();
		for (String string : arr) {
			System.out.println(string);
		}
	}
}
           

執行後列印的控制台結果:

true
true
測試
修改
           

從測試結果看出 arr 集合的引用位址是相同的,意味着複制的不是值,而是引用的位址。這樣的話,如果我們修改任意一個對象中的引用資料類型,shallowPrototype 和clone 的 arr 值(List)都會改變。這就是我們常說的淺克隆。隻是完整複制了值的資料,沒有複制引用對象本身去建立新的引用對象。換言之,所有的引用對象仍然指向原來的對象。

JDK中也預設提供了Object的clone方法,隻需在要求可以克隆的類上實作Cloneable接口,寫出clone方法,調用super.clone即可實作淺克隆,例如:

//實作Cloneable接口
public class PrototypeClone implements Cloneable{

	private int id;
	
	private String name;
	
	private String type;
	
	private List<String> arr;

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

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

	public String getType() {
		return type;
	}

	public void setType(String type) {
		this.type = type;
	}

	public List<String> getArr() {
		return arr;
	}

	public void setArr(List<String> arr) {
		this.arr = arr;
	}

	public PrototypeClone(int id, String name, String type, List<String> arr) {
		super();
		this.id = id;
		this.name = name;
		this.type = type;
		this.arr = arr;
	}
	
	public PrototypeClone clone() {
		try {
			//調用super.clone方法
			return (PrototypeClone) super.clone();
		} catch (CloneNotSupportedException e) {
			e.printStackTrace();
		}
		return null;
	}
}
           

建立測試類:

public class TestJdkClone {

	public static void main(String[] args) {
		PrototypeClone prototypeClone = new PrototypeClone(1, "123", "1", new ArrayList<String>() {{add("ceshi");}});
		PrototypeClone clone = prototypeClone.clone();
		System.out.println(prototypeClone.getArr() == clone.getArr());
	}
}
           

列印結果:

true
           

說明JDK提供的clone也為淺克隆。

4、深克隆

深克隆就是僅僅克隆一個對象的值,而不克隆其引用位址,相當于重新在堆記憶體中開辟一塊空間配置設定個該對象中的屬性。

實作方式我們可以采用序列化與反序列化來實作。

同樣建立接口:

public interface DeepCloneInterface {
	public Object cloneObject();
}
           

建立實作類,實作DeepCloneInterface 以及 Serializable接口(為了反序列化)

public class DeepPrototype implements DeepCloneInterface,Serializable{
	
	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	private int id;
	
	private String name;
	
	private List<String> arr;
	
	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

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

	public List<String> getArr() {
		return arr;
	}

	public void setArr(List<String> arr) {
		this.arr = arr;
	}
	 
	//序列化與反序列化實作深克隆
	@Override
	public Object cloneObject() {
		try {
			ByteArrayOutputStream bos = new ByteArrayOutputStream();
			ObjectOutputStream oos = new ObjectOutputStream(bos);
			
			oos.writeObject(this);
			ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
			ObjectInputStream ois = new ObjectInputStream(bis);
			Object readObject = ois.readObject();
			
			ois.close();
			bis.close();
			oos.flush();
			oos.close();
			bos.close();
			return readObject;
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

}
           

測試類:

public class DeepTest {

	public static void main(String[] args) {
		DeepPrototype deepPrototype = new DeepPrototype();
		deepPrototype.setArr(new ArrayList<String>() {{add("測試");}});
		deepPrototype.setId(1);
		deepPrototype.setName("測試");
		
		
		DeepPrototype cloneObject = (DeepPrototype)deepPrototype.cloneObject();
		
		System.out.println(cloneObject.getArr() == deepPrototype.getArr());
	}
}
           

列印結果:

false
           

可以看到深克隆下,克隆的僅僅為值,并不會去克隆引用類型的配置設定位址。

5、克隆破壞單例模式

如果我們克隆的目标的對象是單例對象,那意味着,深克隆就會破壞單例。實際上防止克隆破壞單例解決思路非常簡單,禁止深克隆便可。要麼單例類不實作Cloneable 接口;要麼我們重寫 clone()方法,在 clone 方法中傳回單例對象即可,具體如下:

....
@Override
protected Object clone() {
	return 具體的單例執行個體對象;
}
....