4. 設計模式–原型模式

  • 2022 年 10 月 11 日
  • 筆記

原型模式

  在java中我們知道通過new關鍵字創建的對象是非常繁瑣的(類加載判斷,內存分配,初始化等),在我們需要大量對象的情況下,原型模式就是我們可以考慮實現的方式。

原型模式我們也稱為克隆模式,即一個某個對象為原型克隆出來一個一模一樣的對象,該對象的屬性和原型對象一模一樣。而且對於原型對象沒有任何影響。原型模式的克隆方式有兩種:淺克隆和深度克隆

原型模式 說明
淺克隆 只是拷貝本對象,其對象內部的數組、引用對象等都不拷貝,還是指向原生對象的內部元素地址
深度克隆 深複製把要複製的對象所引用的對象都複製了一遍

淺克隆

被複制對象的所有變量都含有與原來的對象相同的值,而所有的對其他對象的引用仍然指向原來的對象。換言之,淺複製僅僅複製所考慮的對象,而不複製它所引用的對象。 Object類提供的方法clone只是拷貝本對象 , 其對象內部的數組、引用對象等都不拷貝,還是指向原生對象的內部元素地址

實現

被克隆的對象必須Cloneable,Serializable這兩個接口

原型類

package com.dpb.prototype;

import java.io.Serializable;
import java.util.Date;

/**
 * 原型類:被克隆的類型
 * @author dengp
 *
 */
public class User implements Cloneable,Serializable{
	
	private String name;
	
	private Date birth;
	
	private int age;

	public String getName() {
		return name;
	}

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

	public Date getBirth() {
		return birth;
	}

	public void setBirth(Date birth) {
		this.birth = birth;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}
	
	/**
	 * 實現克隆的方法
	 */
	public Object clone() throws CloneNotSupportedException{
		return super.clone();
	}
}

測試類

public static void main(String[] args) throws CloneNotSupportedException {
	Date date =  new Date(1231231231231l);
	User user = new User();
	user.setName("波波烤鴨");
	user.setAge(18);
	user.setBirth(date);
	System.out.println("----輸出原型對象的屬性------");
	System.out.println(user);
	System.out.println(user.getName());
	System.out.println(user.getBirth());
	// 克隆對象
	User user1 =(User) user.clone();
	// 修改原型對象中的屬性
	date.setTime(123231231231l);
	System.out.println(user.getBirth());
	
	// 修改參數
	user1.setName("dpb");
	System.out.println("-------克隆對象的屬性-----");
	System.out.println(user1);
	System.out.println(user1.getName());
	System.out.println(user1.getBirth());
}

淺克隆的問題:雖然產生了兩個完全不同的對象,但是被複制的對象的所有變量都含有與原來的對象相同的值,而所有的對其他對象的引用都仍然指向原來的對象。

深度克隆

被複制對象的所有變量都含有與原來的對象相同的值,除去那些引用其他對象的變量。那些引用其他對象的變量將指向被複制過的新對象,而不再是原有的那些被引用的對象。換言之,深複製把要複製的對象所引用的對象都複製了一遍。
實現的效果是:

深度克隆(deep clone)有兩種實現方式,第一種是在淺克隆的基礎上實現,第二種是通過序列化和反序列化實現,我們分別來介紹

第一種方式

在淺克隆的基礎上實現
原型類:

package com.dpb.prototype;

import java.io.Serializable;
import java.util.Date;

/**
 * 原型類:被克隆的類型
 * 深度克隆測試
 * @author dengp
 *
 */
public class User2 implements Cloneable,Serializable{
	
	private String name;
	
	private Date birth;
	
	private int age;

	public String getName() {
		return name;
	}

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

	public Date getBirth() {
		return birth;
	}

	public void setBirth(Date birth) {
		this.birth = birth;
	}

	public int getAge() {
		return age;
	}

	public void setAge(int age) {
		this.age = age;
	}
	
	/**
	 * 實現克隆的方法
	 * 深度克隆(deep clone)
	 */
	public Object clone() throws CloneNotSupportedException{
		Object object = super.clone();
		// 實現深度克隆(deep clone)
		User2 user = (User2)object;
		user.birth = (Date) this.birth.clone();
		return object;
	}
}

測試代碼

public static void main(String[] args) throws CloneNotSupportedException {
	Date date =  new Date(1231231231231l);
	User2 user = new User2();
	user.setName("波波烤鴨");
	user.setAge(18);
	user.setBirth(date);
	System.out.println("----輸出原型對象的屬性------");
	System.out.println(user);
	System.out.println(user.getName());
	System.out.println(user.getBirth());
	// 克隆對象
	User2 user1 =(User2) user.clone();
	// 修改原型對象中的屬性
	date.setTime(123231231231l);
	System.out.println(user.getBirth());
	
	// 修改參數
	user1.setName("dpb");
	System.out.println("-------克隆對象的屬性-----");
	System.out.println(user1);
	System.out.println(user1.getName());
	System.out.println(user1.getBirth());
}

我們發現克隆的對象的屬性並沒有隨着我們對Date的修改而改變,說明克隆對象的Date屬性和原型對象的Date屬性引用的不是同一個對象,實現的深度複製。

第二種方式:序列化和反序列化

名稱 說明
序列化 把對象轉換為位元組序列的過程。
反序列化 把位元組序列恢復為對象的過程。
public static void main(String[] args) throws CloneNotSupportedException, Exception {
	Date date =  new Date(1231231231231l);
	User user = new User();
	user.setName("波波烤鴨");
	user.setAge(18);
	user.setBirth(date);
	System.out.println("-----原型對象的屬性------");
	System.out.println(user);
	System.out.println(user.getName());
	System.out.println(user.getBirth());
	
	//使用序列化和反序列化實現深複製
	ByteArrayOutputStream bos = new ByteArrayOutputStream();
	ObjectOutputStream    oos = new ObjectOutputStream(bos);
	oos.writeObject(user);
	byte[] bytes = bos.toByteArray();
	
	ByteArrayInputStream  bis = new ByteArrayInputStream(bytes);
	ObjectInputStream	  ois = new ObjectInputStream(bis);
	
	//克隆好的對象!
	User user1 = (User) ois.readObject();   
	
	// 修改原型對象的值
	date.setTime(221321321321321l);
	System.out.println(user.getBirth());
	
	System.out.println("------克隆對象的屬性-------");
	System.out.println(user1);
	System.out.println(user1.getName());
	System.out.println(user1.getBirth());
}

實現了和第一種實現方式相同的效果~實現了深度克隆

原型模式和直接new對象方式的比較

當我們需要大量的同一類型對象的時候可以使用原型模式,下面是兩種方式的性能對比:

用兩種方式同時生成10個對象

/**
 * 測試普通new方式創建對象和clone方式創建對象的效率差異!
 * 如果需要短時間創建大量對象,並且new的過程比較耗時。則可以考慮使用原型模式!
 * @author 波波烤鴨
 *
 */
public class Client4 {
	
	public static void testNew(int size){
		long start = System.currentTimeMillis();
		for(int i=0;i<size;i++){
			User t = new User();
		}
		long end = System.currentTimeMillis();
		System.out.println("new的方式創建耗時:"+(end-start));
	}
	
	public static void testClone(int size) throws CloneNotSupportedException{
		long start = System.currentTimeMillis();
		User t = new User();
		for(int i=0;i<size;i++){
			User temp = (User) t.clone();
		}
		long end = System.currentTimeMillis();
		System.out.println("clone的方式創建耗時:"+(end-start));
	}
	
	
	public static void main(String[] args) throws Exception {	
		testNew(10);
		testClone(10);
	}
}


class User implements Cloneable {  //用戶
	public User() {
		try {
			Thread.sleep(10);  //模擬創建對象耗時的過程!
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	
	@Override
	protected Object clone() throws CloneNotSupportedException {
		Object obj = super.clone();  //直接調用object對象的clone()方法!
		return obj;
	}
}

輸出結果:

new的方式創建耗時:108
clone的方式創建耗時:11

用兩種方式同時生成1000個對象


/**
 * 測試普通new方式創建對象和clone方式創建對象的效率差異!
 * 如果需要短時間創建大量對象,並且new的過程比較耗時。則可以考慮使用原型模式!
 * @author 波波烤鴨
 *
 */
public class Client4 {
	
	public static void testNew(int size){
		long start = System.currentTimeMillis();
		for(int i=0;i<size;i++){
			User t = new User();
		}
		long end = System.currentTimeMillis();
		System.out.println("new的方式創建耗時:"+(end-start));
	}
	
	public static void testClone(int size) throws CloneNotSupportedException{
		long start = System.currentTimeMillis();
		User t = new User();
		for(int i=0;i<size;i++){
			User temp = (User) t.clone();
		}
		long end = System.currentTimeMillis();
		System.out.println("clone的方式創建耗時:"+(end-start));
	}
	
	
	public static void main(String[] args) throws Exception {	
		testNew(1000);
		testClone(1000);
	}
}


class User implements Cloneable {  //用戶
	public User() {
		try {
			Thread.sleep(10);  //模擬創建對象耗時的過程!
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	
	@Override
	protected Object clone() throws CloneNotSupportedException {
		Object obj = super.clone();  //直接調用object對象的clone()方法!
		return obj;
	}
}

輸出結果:

new的方式創建耗時:10836
clone的方式創建耗時:10

小結:通過clone的方式在獲取大量對象的時候性能開銷基本沒有什麼影響,而new的方式隨着實例的對象越來越多,性能會急劇下降,所以原型模式是一種比較重要的獲取實例的方式。大家應該掌握好。

開發中的應用場景

原型模式很少單獨出現,一般是和工廠方法模式一起出現,通過clone的方法創建一個對象,然後由工廠方法提供給調用者。
• spring中bean的創建實際就是兩種:單例模式和原型模式。(原型模式需要和工廠模式搭配起來)