Java設計模式之創建型模式:原型模式

原型模式介紹

原型模式是一種創建型的設計模式,原型模式是指複製現有的實例來創建新的實例,不需要知道實例的創建細節。(在Java中意味使用clone方法或者序列化和反序列化)。

當創建給定類的實例的過程很昂貴或很複雜時,就需要使用原型模式。

原型模式的原理UML類圖,如下:
在這裏插入圖片描述
原理結構圖說明:

  • ProtoType:原型類,實現Cloneable接口,聲明克隆自己的
    抽象方法。
  • ConcreteProtoType:具體的原型類,實現克隆自己的方法。
  • Client:其中使用ProtoType,讓一個原型對象克隆自己,從而創建一個新的對象。

clone()方法說明:

Object中clone()方法是protected的,是淺拷貝,要使用clone()方法,要重寫它,只有實現了Cloneable接口纔可以調用該方法,否則會拋出CloneNotSupportedException異常。

clone()方法在實現上是在內存上直接複製二進制流,然後重新分配內存空間,比new出來的效率要高,clone的過程不會調用構造函數,也就是說通過這一方法創造出來的對象不會由構造函數的處理。

clone()方法創建並返回此對象的一個副本。對於任何對象x,滿足以下表達式:

  1. x.clone() != x爲true
  2. x.clone().getClass() == x.getClass()爲true
  3. x.clone().equals(x)一般情況下爲true,但這並不是必須要滿足的要求

淺拷貝介紹:

  1. 對於數據類型爲基本數據類型的成員變量,淺拷貝會直接進行值傳遞,也就是將該屬性值複製一份給新的對象。無論是淺拷貝還是深拷貝,拷貝出的新的對象指向新的地址空間。
  2. 對於數據類型爲引用數據類型的成員變量,如數組,對象。那麼淺拷貝將進行引用傳遞,也就是隻是將成員變量的引用值(內存地址)複製一份給新的對象。在這種情況下,若成員變量是數組或對象,因爲兩個對象的成員變量指向同一實例,在一個對象中修改該成員變量會影響到另一個對象的成員變量。

實現原型模式示例

場景:現在有一隻羊名爲li,大小爲45斤,顏色爲白色。朋友爲一個叫kai的羊,編程實現複製十隻一模一樣的羊。

下面創建一個羊的實體類,實現cloneable接口並重寫clone()方法:

public class Sheep implements Cloneable {
	private String name;
	private String color;
	private int size;
	public Sheep friend;

	public Sheep(String name, String color, int size) {
		this.name = name;
		this.color = color;
		this.size = size;
	}

	@Override
	public String toString() {
		return "Sheep [name=" + name + ", color=" + color + ", size=" + size + "]";
	}

	@Override
	protected Sheep clone() {
		Sheep sheep = null;
		try {
			sheep = (Sheep) super.clone();
		} catch (CloneNotSupportedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return sheep;
	}
}

在Client類中完成複製Sheep對象
這裏很容易想到使用原型對象的屬性來new一個對象,但是這種方法總是需要重新獲取原始對象的屬性,如果創建的對象複雜就會效率很低,另外如果原型對象有修改,可能會需要修改代碼。

public class Client {
	public static void main(String[] args) {
		Sheep sheep = new Sheep("li", "white", 40);
		sheep.friend = new Sheep("kai", "black", 45);
		System.out.println(sheep.toString() + sheep.friend.hashCode());
		Sheep copySheep = (Sheep) sheep.clone();
		System.out.println("複製的羊:" + copySheep.toString() + copySheep.friend.hashCode());
	}
}

運行程序查看結果:
在這裏插入圖片描述
可以看到兩個對象屬性friend的hashcode的值一致,即引用的對象是同一個,這說明上面使用的拷貝方式是淺拷貝。

深拷貝

1.基本介紹

深拷貝也就是複製對象的所有基本數據類型的成員變量,對於所有的引用類型的成員變量,深拷貝會爲其申請存儲空間,並會複製每個引用類型的成員變量所引用的對象。

也就是說對象進行深拷貝是要對整個對象進行拷貝

深拷貝的兩種實現方式:重寫clone()方法和對象序列化。

2.深拷貝應用

1.方式1:重寫clone()方法。
新建一個類DeepCloneableTarget 作爲成員變量,該類的屬性只有String類型,雖然String類型也屬於引用類型,但因爲String的不可變性(可以使用反射來修改String的值),這裏使用Object類的clone即可。

public class DeepCloneableTarget implements Cloneable {
	private String cloneName;

	public DeepCloneableTarget(String cloneName) {
		super();
		this.cloneName = cloneName;
	}
	@Override
	protected Object clone() throws CloneNotSupportedException {
		// TODO Auto-generated method stub
		return super.clone();
	}
}

類 DeepProtoType 將 DeepCloneableTarget類對象作爲成員變量。

public class DeepProtoType implements Cloneable {
	private String name;
	public DeepCloneableTarget deepCloneableTarget;

	public DeepProtoType(String name, DeepCloneableTarget deepCloneableTarget) {
		super();
		this.name = name;
		this.deepCloneableTarget = deepCloneableTarget;
	}
	@Override
	protected DeepProtoType clone() throws CloneNotSupportedException {
		// TODO Auto-generated method stub
		DeepProtoType deep = (DeepProtoType) super.clone();
		// 對引用類型做單獨處理,單獨進行克隆
		deep.deepCloneableTarget = (DeepCloneableTarget) deepCloneableTarget.clone();
		return deep;
	}
}

Client類測試是否完成成員變量DeepCloneableTarget類對象的拷貝。

public class Client {
	public static void main(String[] args) throws CloneNotSupportedException {
		DeepCloneableTarget deepCloneableTarget = new DeepCloneableTarget("deppTarget");
		DeepProtoType deepProtoType = new DeepProtoType("protoType", deepCloneableTarget);
		DeepProtoType clone = deepProtoType.clone();
		System.out.println(deepProtoType.deepCloneableTarget.hashCode());
		System.out.println(clone.deepCloneableTarget.hashCode());
	}
}

通過運行結果中的hashcode的值的不同判斷兩個作爲成員變量的對象是不同的即完成了深拷貝。

2.方式2:通過對象的序列化實現

類DeepCloneableTarget改爲實現Serializable接口

public class DeepCloneableTarget implements Serializable {
	private static final long serialVersionUID = 1L;
	private String cloneName;

	public DeepCloneableTarget(String cloneName) {
		super();
		this.cloneName = cloneName;
	}
}

類DeepProtoType也實現Serializable 接口,定義deepClone()方法使用序列化和反序列化實現深拷貝。

public class DeepProtoType implements Serializable {
	private static final long serialVersionUID = 1L;
	public String name;
	public DeepCloneableTarget deepCloneableTarget;

	public DeepProtoType(String name, DeepCloneableTarget deepCloneableTarget) {
		super();
		this.name = name;
		this.deepCloneableTarget = deepCloneableTarget;
	}
	public DeepProtoType 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
				e2.printStackTrace();

			}
		}

	}
}

測試是否能完成深拷貝。

public class Client {
	public static void main(String[] args) throws CloneNotSupportedException {
		DeepCloneableTarget deepCloneableTarget = new DeepCloneableTarget("deppTarget");
		DeepProtoType deepProtoType = new DeepProtoType("protoType", deepCloneableTarget);
		DeepProtoType clone = deepProtoType.deepClone();
		System.out.println(deepProtoType.name + " " + deepProtoType.deepCloneableTarget.hashCode());
		System.out.println(deepProtoType.name + " " + clone.deepCloneableTarget.hashCode());
	}
}

運行結果如下:
在這裏插入圖片描述
兩個對象的成員變量中的DeepCloneableTarget 對象的hashcode不同,所以兩個變量是不同的兩個對象,這同樣實現了深拷貝。

原型模式在Spring中的應用

在Spring 中我們可以定義Bean的作用域爲prototype,下面使用Java配置類的方式來註冊一個Bean。

@Configuration
public class SpringConfig {

	@Bean(value = "person")
	@Scope(value = "prototype")
	// 或@Scope("ConfigurableBeanFactory.SCOPE_PROTOTYPE")
	public Person getPerson() {
		return new Person();
	}
}

當使用getBean()方法獲取容器中的這個Bean時,下面通過Debug來走一遍該Bean的實例化過程:
首先調用AbstractApplicationContext的getBean()方法
在這裏插入圖片描述
這裏的getBeanFactory()是通過子類GenericApplicationContext 中的getBeanFactory()獲取DefaultListableBeanFactory。
在這裏插入圖片描述
然後getBean()方法調用AbstractBeanFactory的getBean()
在這裏插入圖片描述
接着調用本類中的doGetBean()方法,最終在這裏判斷如果是prototype,就調用這個類的createBean()方法。
在這裏插入圖片描述

總結

下面總結一下原型模式:

  • 適用場景:
  1. 當創建新的對象複雜或需要一個類的許多對象時,就可以利用原型模式簡化對象的創建過程來提高效率。
  2. 類初始化需要消耗資源較多,就可以使用原型模式。
  • 優點:
  1. 向客戶隱藏創建新實例的複雜性
  2. 提供讓客戶能夠產生未知類型對象的選項。即客戶在不知道要實例化何種特定類的情況下,可以製造出新的實例。
  3. 不用重新初始化對象,而是動態地獲得對象運行時的狀態。
  • 缺點:實現深克隆時可能需要複雜的代碼。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章