原型模式(ProtoType) - Java裏的對象複製


一, 引用的複製和對象複製.

在編程中, 我們有時會用兩個引用指向同一個對象.


例如:

ArrayList a = new ArrayLIst();
ArrayList b = a;

看起來好像有a,b兩個容器, 實際上a,b是兩個引用, 它們都指向同1個Object的內存地址.


而對象複製是指:

在內存裏劃分一塊與指定對象相同內容的內存.


也就是說內存裏原理有1個Object的內存, 複製後就有兩個了...




二, 對象複製的簡便方法.

當然, 對象複製的實現十分簡單, 只需要實例化1個具有相同屬性的對象就ok了.

注意, 如果用 "==" 來講比較兩個引用是返回false, 因爲它們的地址不同.


舉個列子:

2.1 產品類Prod:

public class Prod {
	private int prodID;
	private String prodName;
	
	public int getProdID() {
		return prodID;
	}
	public void setProdID(int prodID) {
		this.prodID = prodID;
	}
	
	public String getProdName() {
		return prodName;
	}
	public void setProdName(String prodName) {
		this.prodName = prodName;
	}
	
	@Override
	public String toString(){
		return "Prod: " + this.getProdID() + " , " + this.getProdName();
	}
}


2.2 訂單類Order:

public class Order {
	private int orderID;
	private Prod prod;
	private int amount;
	
	public int getOrderID() {
		return orderID;
	}
	public void setOrderID(int orderID) {
		this.orderID = orderID;
	}
	
	public Prod getProd() {
		return prod;
	}
	public void setProd(Prod prod) {
		this.prod = prod;
	}
	
	public int getAmount() {
		return amount;
	}
	public void setAmount(int amount) {
		this.amount = amount;
	}
	
	public String toString(){
		return "Order: " + this.getOrderID() + " , " + this.getAmount() + " " + this.getProd().toString();
	}
}

2.3 客戶端代碼:

這裏我先實例化1個order類 od1, 然後在複製1個od1 的對象od2

		Prod p1 = new Prod();
		p1.setProdID(1);
		p1.setProdName("Hammer");
		
		Order od1 = new Order();
		od1.setOrderID(1);
		od1.setProd(p1);
		od1.setAmount(20);
		
		Prod p2 = new Prod();
		p2.setProdID(1);
		p2.setProdName("Hammer");
		
		Order od2 = new Order();
		od2.setOrderID(1);
		od2.setProd(p1);
		od2.setAmount(20);
		
		System.out.println(od1);
		System.out.println(od2);


輸出:

Order: 1 , 20 Prod: 1 , Hammer
Order: 1 , 20 Prod: 1 , Hammer



2.4 這種方法的缺點

首先這種寫法不夠優雅, 很多重複代碼.


其次, 這種複製方法調用了類的構造函數.

就如上面的例子, 即使Order類的構造函數是空的, 但是實際上構造函數的執行機制很複雜,.

構造函數最重要的動作就是給類裏的每個成員劃分內存空間. 畢竟java是類c語言, 相當於執行了很多次malloc()函數.


如果某些業務類的構造函數寫的很複雜, 就更耗費資源了.

而且



三, 利用Object.clone()方法來實現對象複製.


java裏所有對象都是直接或簡直繼承自基類Object.

而clone()是基類的Native 方法, 所謂Native方法可以認爲是java 內部特定方法, 它比非Native的執行效率要好.



也就是說, 利用Object.clone() 方法會比實例化1個相同內容對象效率要好.


3.1 Object.clone()方法和 Cloneable接口:

Object.clone() 方法有點特殊,  首先它是public方法, 也就是說它可以通過引用名和"."來調用.

但是它不能被子類繼承, 也就說, 出了Object類外所有java裏的class裏面都沒有clone()這個方法的.


如果要使用Object.clone()方法.

只能引用1個叫做 Cloneable裏的Interface,  然後重寫Cloneable接口裏的clone()方法, 在裏面調用object.clone().


這個接口就起了1個標記的作用, 幫組實現多態.



3.2 訂單類Order:

我們按在這個思路來修改上面的訂單類Order:

public class Order implements Cloneable{
	private int orderID;
	private Prod prod;
	private int amount;
	
	public int getOrderID() {
		return orderID;
	}
	public void setOrderID(int orderID) {
		this.orderID = orderID;
	}
	
	public Prod getProd() {
		return prod;
	}
	public void setProd(Prod prod) {
		this.prod = prod;
	}
	
	public int getAmount() {
		return amount;
	}
	public void setAmount(int amount) {
		this.amount = amount;
	}
	
	public String toString(){
		return "Order: " + this.getOrderID() + " , " + this.getAmount() + " " + this.getProd().toString();
	}
	
	@Override
	public Object clone(){
		Object o = null;
		try{
			o = super.clone();
		}catch(Exception e){
			e.printStackTrace();
		}
		
		return o;
	}
}

值得注意的是, Object.clone() 回拋異常.

而, Prod類沒有任何修改.


3.3 客戶端代碼:

Prod p1 = new Prod();
		p1.setProdID(2);
		p1.setProdName("knife");
		
		Order od1 = new Order();
		od1.setOrderID(2);
		od1.setAmount(30);
		od1.setProd(p1);
		
		Order od2 = (Order)od1.clone();
		System.out.println(od1);
		System.out.println(od2);


可見, 只需要執行一句
Order od2 = (Order)od1.clone();

就相當於 複製了1個對象.


3.4 UML圖

我們來看看這個例子的UML:


實際上這個就是原型模式(prototype)的UML圖了,

原來我們不知不覺得使用了原型模式.



四, 原型模式的定義


原型模式(protoType), 用原型實例制定創建對象的種類, 並且通過copy這些原型創建新的對象.


總覺得設計模式的定義都太過於簡單噁心了.


上面的

原型指的是  Cloneable這個接口.

原型實例指的是 Order這個類.



五, 淺複製和深複製

對象複製也分兩種.


淺複製:

           兩個對象中的值類型成員屬性相等,  對象成員成員屬性實際上沒有複製,都是同一個對象.


深複製:

          兩個對象中的值類型成員屬性相等,  對象成員屬性指向不同的 具有相同成員的 對象.



如上面的例子, 稍稍修改客戶端代碼:

		Prod p1 = new Prod();
		p1.setProdID(2);
		p1.setProdName("knife");
		
		Order od1 = new Order();
		od1.setOrderID(2);
		od1.setAmount(30);
		od1.setProd(p1);
		
		Order od2 = (Order)od1.clone();
		System.out.println(od1);
		System.out.println(od2);
		
		od1.setOrderID(3);
		od1.setAmount(40);
		od1.getProd().setProdName("blade");
		
		System.out.println(od1);
		System.out.println(od2);

上面 構建了對象od1,

然後複製出了另1個對象od2,


然後修改od1的 值成員 和 對象成員

然後輸出od1, 和 od2 成員的值:

Order: 2 , 30 Prod: 2 , knife
Order: 2 , 30 Prod: 2 , knife
Order: 3 , 40 Prod: 2 , blade
Order: 2 , 30 Prod: 2 , blade


可見, 當od1 的id 和 amount的值被改成 3 和 40 後,  od2 的id和 Amount的值並不受影響, 因爲它們的值類型成員屬性是相互獨立的.

但是當od1 的 prod屬性的內容被修改後,    od2 的也被修改了(knife - > blade),  因爲 od1 和 od2 的 prod成員指向的都是同1個對象.


這就證明了:

Java裏的 Object.clone()方法實現的是淺複製.


六, 原型模式的深複製實現

接下來再次修改上面的例子, 令其實現成深複製

而且, 根據項目需要, 複製並不會複製Order的id, 也就是訂單號碼是唯一的, 只複製Prod和數量.


6.1 訂單類Order:

public class Order implements Cloneable{
	private int orderID;
	private Prod prod;
	private int amount;
	
	public int getOrderID() {
		return orderID;
	}
	public void setOrderID(int orderID) {
		this.orderID = orderID;
	}
	
	public Prod getProd() {
		return prod;
	}
	public void setProd(Prod prod) {
		this.prod = prod;
	}
	
	public int getAmount() {
		return amount;
	}
	public void setAmount(int amount) {
		this.amount = amount;
	}
	
	public String toString(){
		return "Order: " + this.getOrderID() + " , " + this.getAmount() + " " + this.getProd().toString();
	}
	
	@Override
	public Object clone(){
		Prod p = new Prod();
		p.setProdID(this.getProd().getProdID());
		p.setProdName(this.getProd().getProdName());
		
		Order o = new Order();
		o.setOrderID(this.getOrderID() + 1);
		o.setAmount(this.getAmount());
		o.setProd(p);
		
		return o;
	}
}

客戶端輸出:

Order: 2 , 30 Prod: 2 , knife
Order: 3 , 30 Prod: 2 , knife
Order: 3 , 40 Prod: 2 , blade
Order: 3 , 30 Prod: 2 , knife


可以, 無論od1修改了什麼,  od2 都不受影響


這種方法放棄了Object.clone(), Order類在重寫clone()方法裏還是使用了構造方法去實例化1個新的對象.


這種方法配置更加靈活(選擇性地複製成員)

當時放棄了Object.clone()的效能優點.

但是這仍然實現了原型模式(protoType)

























發佈了288 篇原創文章 · 獲贊 300 · 訪問量 63萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章