2020-12-11——原型模式在JDK以及Spring原始碼中如何進行串聯

蒙奇D灬小武發表於2020-12-12

建立型模式

目錄

1、原型模式

1.1 原型模式UML圖

1.2 日常生活中看原型模式

1.3 使用場景

1.4 具體例子

1.4.1 場景

1.4.2 程式碼示例

2、原型模式在原始碼中的應用

2.1 JDK原始碼中原型模式

2.2 Spring原始碼中原型模式

3、 深拷貝的實現方式

4、原型模式優缺點

4.1 優點

4.2 缺點

4.3 注意


1、原型模式

原型模式(Prototype Pattern)是用於建立重複的物件,同時又能保證效能。這種型別的設計模式屬於建立型模式,它提供了一種建立物件的最佳方式。這種模式是實現了一個原型介面,該介面用於建立當前物件的克隆。當直接建立物件的代價比較大時,則採用這種模式。拷貝克隆比new快(不一定見得,只有new構造物件較為耗時或成本很高,才考慮原型模式)。

例如,一個物件需要在一個高代價的資料庫操作之後被建立。我們可以快取該物件,在下一個請求時返回它的克隆,在需要的時候更新資料庫,以此來減少資料庫呼叫。

意圖:用原型例項指定建立物件的種類,並且通過拷貝這些原型建立新的物件。

主要解決:在執行期建立和刪除原型。

何時使用: 

1、當一個系統應該獨立於它的產品建立,構成和表示時。

2、當要例項化的類是在執行時刻指定時,例如,通過動態裝載。

3、為了避免建立一個與產品類層次平行的工廠類層次時。

4、當一個類的例項只能有幾個不同狀態組合中的一種時。建立相應數目的原型並克隆它們可能比每次用合適的狀態手工例項化該類更方便一些。

如何解決:利用已有的一個原型物件,快速地生成和原型物件一樣的例項。

關鍵程式碼: 

1、實現克隆操作,在 JAVA 繼承 Cloneable,重寫 clone(),在 .NET 中可以使用 Object 類的 MemberwiseClone() 方法來實現物件的淺拷貝或通過序列化的方式來實現深拷貝。

2、原型模式同樣用於隔離類物件的使用者和具體型別(易變類)之間的耦合關係,它同樣要求這些"易變類"擁有穩定的介面。

應用例項: 1、細胞分裂。 2、JAVA 中的 Object clone() 方法。

1.1 原型模式UML圖

這裡寫圖片描述

1.2 日常生活中看原型模式

  • 簡歷影印
  • 細胞分裂

1.3 使用場景

  • 1、資源優化場景。
  • 2、類初始化需要消化非常多的資源,這個資源包括資料、硬體資源等。
  • 3、效能和安全要求的場景。
  • 4、通過 new 產生一個物件需要非常繁瑣的資料準備或訪問許可權,則可以使用原型模式。
  • 5、一個物件多個修改者的場景。
  • 6、一個物件需要提供給其他物件訪問,而且各個呼叫者可能都需要修改其值時,可以考慮使用原型模式拷貝多個物件供呼叫者使用。
  • 7、在實際專案中,原型模式很少單獨出現,一般是和工廠方法模式一起出現,通過 clone 的方法建立一個物件,然後由工廠方法提供給呼叫者。原型模式已經與 Java 融為渾然一體,大家可以隨手拿來使用。

1.4 具體例子

1.4.1 場景

我們借用每日上班情景來說明這一模式

1.4.2 程式碼示例

原型建立:

package com.prototype.pojo;
 
/**
 * 日常生活類
 * 
 * @author
 * 
 */
public class DayLife implements Cloneable {
	// 構造方法
	public DayLife() {
		System.out.println("-- 執行構造方法了! --");
	}
 
	// 起床
	private String getUp;
	// 坐公交
	private String byBus;
	// 下車,買早餐
	private String getFood;
	// 中午小憩
	private String noon;
	// 下午開始工作
	private String afternoonWork;
	// 下班回家
	private String goHome;
	// 晚上休閒
	private String night;
 
	public String getGetUp() {
		return getUp;
	}
 
	public void setGetUp(String getUp) {
		this.getUp = getUp;
	}
 
	public String getByBus() {
		return byBus;
	}
 
	public void setByBus(String byBus) {
		this.byBus = byBus;
	}
 
	public String getGetFood() {
		return getFood;
	}
 
	public void setGetFood(String getFood) {
		this.getFood = getFood;
	}
 
	public String getNoon() {
		return noon;
	}
 
	public void setNoon(String noon) {
		this.noon = noon;
	}
 
	public String getAfternoonWork() {
		return afternoonWork;
	}
 
	public void setAfternoonWork(String afternoonWork) {
		this.afternoonWork = afternoonWork;
	}
 
	public String getGoHome() {
		return goHome;
	}
 
	public void setGoHome(String goHome) {
		this.goHome = goHome;
	}
 
	public String getNight() {
		return night;
	}
 
	public void setNight(String night) {
		this.night = night;
	}
 
	/**
	 * 列印輸出日常生活資訊
	 */
	public void print() {
		System.out.println(this.getGetUp());
		System.out.println(this.getByBus());
		System.out.println(this.getGetFood());
		System.out.println(this.getNoon());
		System.out.println(this.getAfternoonWork());
		System.out.println(this.getGoHome());
		System.out.println(this.getNight());
	}
 
	/**
	 * clone方法
	 */
	@Override
	public DayLife clone() {
		try {
			// 呼叫超類的clone方法(超類?也沒有整合任何類啊?哪裡來的超類?別忘記了,所有類都是Object的子類哦!)
			return (DayLife) super.clone();
		} catch (Exception e) {
		}
		return null;
	}
 
}

建立生成原型物件的抽象工廠:

package com.prototype.factory;
 
import com.prototype.pojo.DayLife;
 
/**
 * 工廠類
 * 
 * @author
 * 
 */
public interface ILifeFactory {
	/**
	 * 生產DayLife物件
	 * 
	 * @return
	 */
	public DayLife getNewInstance();
}

建立生成原型物件的具體工廠:

package com.prototype.factory.impl;
 
import com.prototype.factory.ILifeFactory;
import com.prototype.pojo.DayLife;
 
/**
 * 工廠實現類
 * 
 * @author
 * 
 */
public class LifeFactoryImpl implements ILifeFactory {
 
	// DayLife物件例項用於初始化
	private static DayLife dayLife = null;
 
	/**
	 * getNewInstance方法實現:
	 * 
	 * 首先判斷dayLife是否為null:
	 * 如果是null,則使用new建立一個DayLife物件,同時設定初始內容,然後賦值給dayLife物件例項,然後返回;
	 * 如果不是null,則使用dayLift的clone方法產生一個新物件並複製給dayLife物件,然後返回
	 */
	@Override
	public DayLife getNewInstance() {
		// 判斷dayLife是否為null
		if (dayLife == null) {
			// 如果為null
			// 輸出是使用new 產生的物件。注意:new這個只使用一次哦!
			System.out.println(" new DayLife !");
			// 設定dayLife內容
			dayLife = new DayLife();
			dayLife.setGetUp("7:00起床");
			dayLife.setByBus("7:30坐公交車");
			dayLife.setGetFood("8:30到公司附近的公交站下車,經過路旁的早餐車時會順便買好早餐一起帶到公司");
			dayLife.setNoon("午餐在公司附近的小餐館解決,然後在辦公室的座椅上小憩一會");
			dayLife.setAfternoonWork("13:30開始了下午的工作");
			dayLife.setGoHome("17:30準時下班");
			dayLife.setNight("晚上休閒娛樂");
		} else {
			// 如果不為null
			// 輸出是使用clone方法產生的物件
			System.out.println(" clone DayLife !");
			// 將clone物件賦值給dayLife ,返回
			dayLife = dayLife.clone();
		}
		return dayLife;
	}
}

每日工作情景展現:

package com;
 
import com.prototype.factory.ILifeFactory;
import com.prototype.factory.impl.LifeFactoryImpl;
import com.prototype.pojo.DayLife;
 
/**
 * 主應用程式
 * 
 * @author
 * 
 */
public class Client {
 
	public static void main(String[] args) {
		// 建立工廠
		ILifeFactory lifeFactory = new LifeFactoryImpl();
		// 列印輸出DayLife預設內容
		lifeFactory.getNewInstance().print();
 
		// 再次獲得DayLife,修改getUp內容後 輸出內容
		System.out.println("------------------------");
		DayLife dayLife = lifeFactory.getNewInstance();
		dayLife.setGetUp("早上賴床了!7::15才起床!");
		dayLife.print();
 
		// 再次獲得DayLife,修改getUp內容後 輸出內容
		// System.out.println("------------------------");
		// DayLife dayLife2 = lifeFactory.getNewInstance();
		// dayLife2.setGetUp("早上賴床了!7::30才起床!");
		// dayLife2.print();
	}
}

 執行結果:

 new DayLife !
-- 執行構造方法了! --
7:00起床
7:30坐公交車
8:30到公司附近的公交站下車,經過路旁的早餐車時會順便買好早餐一起帶到公司
午餐在公司附近的小餐館解決,然後在辦公室的座椅上小憩一會
13:30開始了下午的工作
17:30準時下班
晚上休閒娛樂
------------------------
 clone DayLife !
早上賴床了!7::15才起床!
7:30坐公交車
8:30到公司附近的公交站下車,經過路旁的早餐車時會順便買好早餐一起帶到公司
午餐在公司附近的小餐館解決,然後在辦公室的座椅上小憩一會
13:30開始了下午的工作
17:30準時下班
晚上休閒娛樂

2、原型模式在原始碼中的應用

2.1 JDK原始碼中原型模式

我們先看JDK 中的 Cloneable 介面。

public interface Cloneable {
}

我們只需要在原始碼中看哪些類實現了 Cloneable 介面即可。下面是 ArrayList 類的實現程式碼。

public Object clone() {
    try {
        @SuppressWarnings("unchecked")
        ArrayList<E> v = (ArrayList<E>) super.clone();
        v.elementData = Arrays.copyOf(elementData, size);
        v.modCount = 0;
        return v;
    } catch (CloneNotSupportedException e) {
        // this shouldn't happen, since we are Cloneable
        throw new InternalError();
    }
}

從以上程式碼可以看出,clone() 方法只是將 List 中的元素迴圈遍歷了一遍。此時,再思考一下,是不是這種形式就是深克隆呢?

下面用程式碼驗證一下,繼續修改 ConcretePrototype 類,增加一個 deepCloneHobbies() 方法,程式碼如下:

public class ConcretePrototype implements Cloneable,Serializable {
   
    public ConcretePrototype deepCloneHobbies(){
        try {
            ConcretePrototype result = (ConcretePrototype) super.clone();
            result.hobbies = (List) ((ArrayList) result.hobbies).clone();
            return result;
        }catch (CloneNotSupportedException){
            return null;
        }
    }
...
}

客戶端程式碼修改如下:

public static void main(String[] args) {
    ...
    //複製原型物件
    ConcretePrototype cloneType = prototype.deepCloneHobbies();
    ...
}

執行結果如下:

原型物件:ConcretePrototype{age=18,name='C語言中文網',hobbies=[書法, 美術]}
克隆物件:ConcretePrototype{age=18,name='C語言中文網',hobbies=[書法, 美術, 技術控]}

可以發現以上結果與《使用序列化實現深克隆》一節執行結果相同,說明以上形式是深克隆。但是這樣的程式碼是硬編碼。如果在物件中宣告瞭各種集合型別,則每種情況都需要單獨處理。因此,深克隆的寫法一般會直接用序列化來操作。

因此,總結如下,如果需要深克隆,如何編寫Java程式碼呢:

  • 1、自己new物件。在自己的clone方法裡面,該建立就建立,該賦值就賦值。
  • 2、巧妙地利用序列化進行復制。
  • 3、在clone中複製成員物件的時候也呼叫clone,進行層層複製。

2.2 Spring原始碼中原型模式

spring 提供了5種scope分別是singleton、 prototype、 request、 session、global session。

在上文中單例模式中我們講到,spring bean預設是單例模式,當設定為prototype模式時,對於原型(prototype)bean來說當每次請求來的時候直接例項化新的bean,沒有快取以及從快取查的過程。

1 建立ioc的時候建立了一個這樣的容器例項

public Object getBean(String name) throws BeansException {
    this.assertBeanFactoryActive();
    //我們先進入getBeanFactory()方法,看看得到的是哪個BeanFactory,再尋找他的getBean()方法
    return this.getBeanFactory().getBean(name);
}
 
//追蹤發現這是一個抽象方法,應該是由AbstractApplicationContext的子類實現
public abstract ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;

2 往下找getBeanFactory()方法的實現

3 發現在AbstractRefreshableApplicationContext中實現了這個方法,返回的是一個DefaultListableBeanFactory,也就是呼叫了DefaultListableBeanFactory的getBean()方法

private DefaultListableBeanFactory beanFactory;
//實現了getBeanFactory方法,確保了執行緒安全的前提下返回了一個DefaultListableBeanFactory
public final ConfigurableListableBeanFactory getBeanFactory() {
    synchronized(this.beanFactoryMonitor) {
        if (this.beanFactory == null) {
            throw new IllegalStateException("BeanFactory not initialized or already closed - call 'refresh' before accessing beans via the ApplicationContext");
        } else {
            return this.beanFactory;
        }
    }
}

4 去到DefaultListableBeanFactory中沒有找到getBean()方法,於是往他的父類去找

5 從這個bean的父類的父類AbstractBeanFactory可以看到這個getBean(),同時呼叫了核心方法doGetBean();

public Object getBean(String name) throws BeansException {
    return this.doGetBean(name, (Class)null, (Object[])null, false);
}

6 進入到doGetBean()方法可以發現,spring對引數進行了判斷,對應呼叫createBean建立了原型模式的物件

protected <T> T doGetBean(String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly) throws BeansException {
    String beanName = this.transformedBeanName(name);
    Object sharedInstance = this.getSingleton(beanName);
   			.................
 
            if (mbd.isSingleton()) {
                sharedInstance = this.getSingleton(beanName, () -> {
                    try {
                        return this.createBean(beanName, mbd, args);
                    } catch (BeansException var5) {
                        this.destroySingleton(beanName);
                        throw var5;
                    }
                });
                bean = this.getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
            //判斷了是否設定了原型模式
            } else if (mbd.isPrototype()) {
                var11 = null;
 
                Object prototypeInstance;
                try {
                    this.beforePrototypeCreation(beanName);
            	    //進入了原型模式的物件建立
                    prototypeInstance = this.createBean(beanName, mbd, args);
                } finally {
                    this.afterPrototypeCreation(beanName);
                }
 
                bean = this.getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
            } else {
                ...................

3、 深拷貝的實現方式

	//深拷貝:方式一,通過重寫clone方法來實現深拷貝,不推薦
//方式二:通過物件的序列化實現
	public Object deepClone() {
		//建立流
		ByteArrayOutputStream bos = null;
		ObjectOutputStream oos = null;
		ByteArrayInputStream bis =null;
		ObjectInputStream ois = null;
		
		try {
			//序列化
			bos = new ByteArrayOutputStream();
			oos = new ObjectOutputStream(bos);
			oos.writeObject(this);//當前的物件以流的方式輸出
			
			//反序列化
			bis = new ByteArrayInputStream(bos.toByteArray());
			ois = new ObjectInputStream(bis);
			DeepPrototype deepPrototype = (DeepPrototype)ois.readObject();
			
			return deepPrototype;
			
		} catch (Exception e) {
			// TODO: handle exception
			e.printStackTrace();
			return null;
		}finally {
			try {
				bos.close();
				oos.close();
				bis.close();
				ois.close();
			} catch (Exception e2) {
				// TODO: handle exception
			}
		}
		
	}

如上,深拷貝方式1:重寫clone方法,物件裡屬性也clone,以此來達到深拷貝。深拷貝方式2:如上,使用序列化方式。個人覺得簡單一些,轉json也能實現類似效果,是否可以作為第三種方式?

4、原型模式優缺點

4.1 優點

  • (1)原型模式是在記憶體中二進位制流的拷貝,要比直接new一個物件效能好很多,特別是要在一個迴圈體內產生大量物件時,原型模式可能更好的體現其優點。
  • (2)還有一個重要的用途就是保護性拷貝,也就是對某個物件對外可能是隻讀的,為了防止外部對這個只讀物件的修改,通常可以通過返回一個物件拷貝的形式實現只讀的限制。

4.2 缺點

  • (1)這既是它的優點也是缺點,直接在記憶體中拷貝,建構函式是不會執行的,在實際開發中應該注意這個潛在問題。優點是減少了約束,缺點也是減少了約束,需要大家在實際應用時考慮。
  • (2)通過實現Cloneable介面的原型模式在呼叫clone函式構造例項時並不一定比通過new操作速度快,只有當通過new構造物件較為耗時或者說成本較高時,通過clone方法才能夠獲得效率上的提升。
  • (3)**缺陷:**需要為每一個類配備一個克隆方法,對於已經存在的類來說得修改原始碼,這違背了ocp原則

4.3 注意

建構函式不會被執行,需格外注意。

 

參考文章:

https://blog.csdn.net/chengqiuming/article/details/70139285

https://www.runoob.com/design-pattern/prototype-pattern.html

http://c.biancheng.net/view/8383.html

https://blog.csdn.net/qq_27007251/article/details/71773720

https://blog.csdn.net/Y_Mlsy/article/details/107282359

https://blog.csdn.net/weixin_44198475/article/details/106594811

相關文章