1 場(chǎng)景問(wèn)題#
1.1 訂單處理系統(tǒng)##
考慮這樣一個(gè)實(shí)際應(yīng)用:訂單處理系統(tǒng)。
現(xiàn)在有一個(gè)訂單處理的系統(tǒng),里面有個(gè)保存訂單的業(yè)務(wù)功能,在這個(gè)業(yè)務(wù)功能里面,客戶有這么一個(gè)需求:每當(dāng)訂單的預(yù)定產(chǎn)品數(shù)量超過(guò)1000的時(shí)候,就需要把訂單拆成兩份訂單來(lái)保存,如果拆成兩份訂單后,還是超過(guò)1000,那就繼續(xù)拆分,直到每份訂單的預(yù)定產(chǎn)品數(shù)量不超過(guò)1000。至于為什么要拆分,原因是好進(jìn)行訂單的后續(xù)處理,后續(xù)是由人工來(lái)處理,每個(gè)人工工作小組的處理能力上限是1000。
根據(jù)業(yè)務(wù),目前的訂單類型被分成兩種:一種是個(gè)人訂單,一種是公司訂單。現(xiàn)在想要實(shí)現(xiàn)一個(gè)通用的訂單處理系統(tǒng),也就是說(shuō),不管具體是什么類型的訂單,都要能夠正常的處理。
該怎么實(shí)現(xiàn)呢?
1.2 不用模式的解決方案##
來(lái)分析上面要求實(shí)現(xiàn)的功能,有朋友會(huì)想,這很簡(jiǎn)單嘛,一共就一個(gè)功能,沒(méi)什么困難的,真的是這樣嗎?先來(lái)嘗試著實(shí)現(xiàn)看看。
- 定義訂單接口
首先,要想實(shí)現(xiàn)通用的訂單處理,而不關(guān)心具體的訂單類型,那么很明顯,訂單處理的對(duì)象應(yīng)該面向一個(gè)訂單的接口或是一個(gè)通用的訂單對(duì)象來(lái)編程,這里就選用面向訂單接口來(lái)處理吧,先把這個(gè)訂單接口定義出來(lái),示例代碼如下:
/**
* 訂單的接口
*/
public interface OrderApi {
/**
* 獲取訂單產(chǎn)品數(shù)量
* @return 訂單中產(chǎn)品數(shù)量
*/
public int getOrderProductNum();
/**
* 設(shè)置訂單產(chǎn)品數(shù)量
* @param num 訂單產(chǎn)品數(shù)量
*/
public void setOrderProductNum(int num);
}
- 既然定義好了訂單的接口,那么接下來(lái)把各種類型的訂單實(shí)現(xiàn)出來(lái),先看看個(gè)人的訂單實(shí)現(xiàn),示例代碼如下:
/**
* 個(gè)人訂單對(duì)象
*/
public class PersonalOrder implements OrderApi{
/**
* 訂購(gòu)人員姓名
*/
private String customerName;
/**
* 產(chǎn)品編號(hào)
*/
private String productId;
/**
* 訂單產(chǎn)品數(shù)量
*/
private int orderProductNum = 0;
public int getOrderProductNum() {
return this.orderProductNum;
}
public void setOrderProductNum(int num) {
this.orderProductNum = num;
}
public String getCustomerName() {
return customerName;
}
public void setCustomerName(String customerName) {
this.customerName = customerName;
}
public String getProductId() {
return productId;
}
public void setProductId(String productId) {
this.productId = productId;
}
public String toString(){
return "本個(gè)人訂單的訂購(gòu)人是="+this.customerName+",訂購(gòu)產(chǎn)品是="+this.productId+",訂購(gòu)數(shù)量為="+this.orderProductNum;
}
}
再看看企業(yè)訂單的實(shí)現(xiàn),示例代碼如下:
/**
* 企業(yè)訂單對(duì)象
*/
public class EnterpriseOrder implements OrderApi {
/**
* 企業(yè)名稱
*/
private String enterpriseName;
/**
* 產(chǎn)品編號(hào)
*/
private String productId;
/**
* 訂單產(chǎn)品數(shù)量
*/
private int orderProductNum = 0;
public int getOrderProductNum() {
return this.orderProductNum;
}
public void setOrderProductNum(int num) {
this.orderProductNum = num;
}
public String getEnterpriseName() {
return enterpriseName;
}
public void setEnterpriseName(String enterpriseName) {
this.enterpriseName = enterpriseName;
}
public String getProductId() {
return productId;
}
public void setProductId(String productId) {
this.productId = productId;
}
public String toString(){
return "本企業(yè)訂單的訂購(gòu)企業(yè)是="+this.enterpriseName+",訂購(gòu)產(chǎn)品是="+this.productId+",訂購(gòu)數(shù)量為="+this.orderProductNum;
}
}
有些朋友看到這里,可能會(huì)有這樣的疑問(wèn):看上去上面兩種類型的訂單對(duì)象,僅僅是一個(gè)數(shù)據(jù)封裝的對(duì)象,而且還有一些數(shù)據(jù)是相同的,為何不抽出一個(gè)父類來(lái),把共同的數(shù)據(jù)定義在父類里面呢?
這里有兩個(gè)考慮,一個(gè)是:這里僅僅是一個(gè)示意,實(shí)際情況遠(yuǎn)比這復(fù)雜,實(shí)際開(kāi)發(fā)中不會(huì)僅僅是數(shù)據(jù)封裝對(duì)象這么簡(jiǎn)單。另外一個(gè)是:為了后續(xù)示例的重點(diǎn)突出,這里要學(xué)習(xí)的是原型模式,因此就沒(méi)有去抽取父類,以免對(duì)象層級(jí)過(guò)多,影響主題的展示。
- 實(shí)現(xiàn)好了訂單對(duì)象,接下來(lái)看看如何實(shí)現(xiàn)通用的訂單處理,先把訂單處理的對(duì)象大概定義出來(lái),示例代碼如下:
/**
* 處理訂單的業(yè)務(wù)對(duì)象
*/
public class OrderBusiness {
/**
* 創(chuàng)建訂單的方法
* @param order 訂單的接口對(duì)象
*/
public void saveOrder(OrderApi order){
//等待具體實(shí)現(xiàn)
}
}
現(xiàn)在的中心任務(wù)就是要來(lái)實(shí)現(xiàn)這個(gè)saveOrder的方法,傳入的參數(shù)是一個(gè)訂單的接口對(duì)象,這個(gè)方法要實(shí)現(xiàn)的功能:根據(jù)業(yè)務(wù)要求,當(dāng)訂單的預(yù)定產(chǎn)品數(shù)量超過(guò)1000的時(shí)候,就需要把訂單拆成兩份訂單。
那好,來(lái)嘗試著實(shí)現(xiàn)一下,因?yàn)轭A(yù)定的數(shù)量可能會(huì)很大,因此采用一個(gè)while循環(huán)來(lái)處理,直到拆分后訂單的數(shù)量不超過(guò)1000,先把實(shí)現(xiàn)的思路寫出來(lái),示例代碼如下:
public class OrderBusiness {
public void saveOrder(OrderApi order){
//1:判斷當(dāng)前的預(yù)定產(chǎn)品數(shù)量是否大于1000
while(order.getOrderProductNum() > 1000){
//2:如果大于,還需要繼續(xù)拆分
//2.1再新建一份訂單,跟傳入的訂單除了數(shù)量不一樣外,其它都相同
OrderApi newOrder = null;
...
}
}
}
大家會(huì)發(fā)現(xiàn),才剛寫到第二步就寫不下去了,為什么呢?
因?yàn)楝F(xiàn)在判斷需要拆分訂單,也就是需要新建一個(gè)訂單對(duì)象,可是訂單處理對(duì)象面對(duì)的是訂單的接口,它根本就不知道現(xiàn)在訂單具體的類型,也不知道具體的訂單實(shí)現(xiàn),它無(wú)法創(chuàng)建出新的訂單對(duì)象來(lái),也就無(wú)法實(shí)現(xiàn)訂單拆分的功能了。
- 一個(gè)簡(jiǎn)單的解決辦法
有朋友提供了這么一個(gè)解決的思路,他說(shuō):不就是在saveOrder方法里面不知道具體的類型,從而導(dǎo)致無(wú)法創(chuàng)建對(duì)象嗎?很簡(jiǎn)單,使用instanceof來(lái)判斷不就可以了,他還給出了他的實(shí)現(xiàn)示意,示意代碼如下:
public class OrderBusiness {
public void saveOrder(OrderApi order){
while(order.getOrderProductNum() > 1000)
//定義一個(gè)表示被拆分出來(lái)的新訂單對(duì)象
OrderApi newOrder = null;
if(order instanceof PersonalOrder){
//創(chuàng)建相應(yīng)的訂單對(duì)象
PersonalOrder p2 = new PersonalOrder();
//然后進(jìn)行賦值等,省略了
//然后再設(shè)置給newOrder
newOrder = p2;
}else if(order instanceof EnterpriseOrder){
//創(chuàng)建相應(yīng)的訂單對(duì)象
EnterpriseOrder e2 = new EnterpriseOrder();
//然后進(jìn)行賦值等,省略了
//然后再設(shè)置給newOrder
newOrder = e2;
}
//然后進(jìn)行拆分和其它業(yè)務(wù)功能處理,省略了
}
}
}
好像能解決問(wèn)題,對(duì)吧。那我們就來(lái)按照他提供的思路,把這個(gè)通用的訂單處理對(duì)象實(shí)現(xiàn)出來(lái),示例代碼如下:
/**
* 處理訂單的業(yè)務(wù)對(duì)象
*/
public class OrderBusiness {
/**
* 創(chuàng)建訂單的方法
* @param order 訂單的接口對(duì)象
*/
public void saveOrder(OrderApi order){
//根據(jù)業(yè)務(wù)要求,當(dāng)訂單預(yù)定產(chǎn)品數(shù)量超過(guò)1000時(shí),就要把訂單拆成兩份訂單
//當(dāng)然如果要做好,這里的1000應(yīng)該做成常量,這么做是為了演示簡(jiǎn)單
//1:判斷當(dāng)前的預(yù)定產(chǎn)品數(shù)量是否大于1000
while(order.getOrderProductNum() > 1000){
//2:如果大于,還需要繼續(xù)拆分
//2.1再新建一份訂單,跟傳入的訂單除了數(shù)量不一樣外,其它都相同
OrderApi newOrder = null;
if(order instanceof PersonalOrder){
//創(chuàng)建相應(yīng)的新的訂單對(duì)象
PersonalOrder p2 = new PersonalOrder();
//然后進(jìn)行賦值,但是產(chǎn)品數(shù)量為1000
PersonalOrder p1 = (PersonalOrder)order;
p2.setCustomerName(p1.getCustomerName());
p2.setProductId(p1.getProductId());
p2.setOrderProductNum(1000);
//然后再設(shè)置給newOrder
newOrder = p2;
}else if(order instanceof EnterpriseOrder){
//創(chuàng)建相應(yīng)的訂單對(duì)象
EnterpriseOrder e2 = new EnterpriseOrder();
//然后進(jìn)行賦值,但是產(chǎn)品數(shù)量為1000
EnterpriseOrder e1 = (EnterpriseOrder)order;
e2.setEnterpriseName(e1.getEnterpriseName());
e2.setProductId(e1.getProductId());
e2.setOrderProductNum(1000);
//然后再設(shè)置給newOrder
newOrder = e2;
}
//2.2原來(lái)的訂單保留,把數(shù)量設(shè)置成減少1000
order.setOrderProductNum(order.getOrderProductNum()-1000);
//然后是業(yè)務(wù)功能處理,省略了,打印輸出,看一下
System.out.println("拆分生成訂單=="+newOrder);
}
//3:不超過(guò)1000,那就直接業(yè)務(wù)功能處理,省略了,打印輸出,看一下
System.out.println("訂單=="+order);
}
}
- 寫個(gè)客戶端來(lái)測(cè)試一下,示例代碼如下:
public class OrderClient {
public static void main(String[] args) {
//創(chuàng)建訂單對(duì)象,這里為了演示簡(jiǎn)單,直接new了
PersonalOrder op = new PersonalOrder();
//設(shè)置訂單數(shù)據(jù)
op.setOrderProductNum(2925);
op.setCustomerName("張三");
op.setProductId("P0001");
//這里獲取業(yè)務(wù)處理的類,也直接new了,為了簡(jiǎn)單,連業(yè)務(wù)接口都沒(méi)有做
OrderBusiness ob = new OrderBusiness();
//調(diào)用業(yè)務(wù)來(lái)保存訂單對(duì)象
ob.saveOrder(op);
}
}
運(yùn)行結(jié)果如下:
拆分生成訂單==本個(gè)人訂單的訂購(gòu)人是=張三,訂購(gòu)產(chǎn)品是=P0001,訂購(gòu)數(shù)量為=1000
拆分生成訂單==本個(gè)人訂單的訂購(gòu)人是=張三,訂購(gòu)產(chǎn)品是=P0001,訂購(gòu)數(shù)量為=1000
訂單==本個(gè)人訂單的訂購(gòu)人是=張三,訂購(gòu)產(chǎn)品是=P0001,訂購(gòu)數(shù)量為=925
根據(jù)訂單中訂購(gòu)產(chǎn)品的數(shù)量,一份訂單被拆分成了三份。同樣的,你還可以傳入企業(yè)訂單,看看是否能正常滿足功能要求。
1.3 有何問(wèn)題##
看起來(lái),上面的實(shí)現(xiàn)確實(shí)不難,好像也能夠通用的進(jìn)行訂單處理,而不需要關(guān)心訂單的類型和具體實(shí)現(xiàn)這樣的功能。
仔細(xì)想想,真的沒(méi)有關(guān)心訂單的類型和具體實(shí)現(xiàn)嗎?答案是“否定的”。
事實(shí)上,在實(shí)現(xiàn)訂單處理的時(shí)候,上面的實(shí)現(xiàn)是按照訂單的類型和具體實(shí)現(xiàn)來(lái)處理的,就是instanceof的那一段。有朋友可能會(huì)問(wèn),這樣實(shí)現(xiàn)有何不可嗎?這樣的實(shí)現(xiàn)有如下幾個(gè)問(wèn)題:
既然想要實(shí)現(xiàn)通用的訂單處理,那么對(duì)于訂單處理的實(shí)現(xiàn)對(duì)象,是不應(yīng)該知道訂單的具體實(shí)現(xiàn)的,更不應(yīng)該依賴訂單的具體實(shí)現(xiàn)。但是上面的實(shí)現(xiàn)中,很明顯訂單處理的對(duì)象依賴了訂單的具體實(shí)現(xiàn)對(duì)象。
這種實(shí)現(xiàn)方式另外一個(gè)問(wèn)題就是:難以擴(kuò)展新的訂單類型。假如現(xiàn)在要加入一個(gè)大客戶專用訂單的類型,那么就需要修改訂單處理的對(duì)象,要在里面添加對(duì)新的訂單類型的支持,這算哪門子的通用處理。
因此,上面的實(shí)現(xiàn)是不太好的,把上面的問(wèn)題再抽象描述一下:已經(jīng)有了某個(gè)對(duì)象實(shí)例后,如何能夠快速簡(jiǎn)單地創(chuàng)建出更多的這種對(duì)象?比如上面的問(wèn)題,就是已經(jīng)有了訂單接口類型的對(duì)象實(shí)例,然后在方法中需要?jiǎng)?chuàng)建出更多的這種對(duì)象。怎么解決呢?
2 解決方案#
2.1 原型模式來(lái)解決##
用來(lái)解決上述問(wèn)題的一個(gè)合理的解決方案就是原型模式。那么什么是原型模式呢?
- 原型模式定義
- 應(yīng)用原型模式來(lái)解決的思路
仔細(xì)分析上面的問(wèn)題,在saveOrder方法里面,已經(jīng)有了訂單接口類型的對(duì)象實(shí)例,是從外部傳入的,但是這里只是知道這個(gè)實(shí)例對(duì)象的種類是訂單的接口類型,并不知道其具體的實(shí)現(xiàn)類型,也就是不知道它到底是個(gè)人訂單還是企業(yè)訂單,但是現(xiàn)在需要在這個(gè)方法里面創(chuàng)建一個(gè)這樣的訂單對(duì)象,看起來(lái)就像是要通過(guò)接口來(lái)創(chuàng)建對(duì)象一樣。
原型模式就可以解決這樣的問(wèn)題,原型模式會(huì)要求對(duì)象實(shí)現(xiàn)一個(gè)可以“克隆”自身的接口,這樣就可以通過(guò)拷貝或者是克隆一個(gè)實(shí)例對(duì)象本身,來(lái)創(chuàng)建一個(gè)新的實(shí)例。如果把這個(gè)方法定義在接口上,看起來(lái)就像是通過(guò)接口來(lái)創(chuàng)建了新的接口對(duì)象。
這樣一來(lái),通過(guò)原型實(shí)例創(chuàng)建新的對(duì)象,就不再需要關(guān)心這個(gè)實(shí)例本身的類型,也不關(guān)心它的具體實(shí)現(xiàn),只要它實(shí)現(xiàn)了克隆自身的方法,就可以通過(guò)這個(gè)方法來(lái)獲取新的對(duì)象,而無(wú)須再去通過(guò)new來(lái)創(chuàng)建。
2.2 模式結(jié)構(gòu)和說(shuō)明##
Prototype:聲明一個(gè)克隆自身的接口,用來(lái)約束想要克隆自己的類,要求它們都要實(shí)現(xiàn)這里定義的克隆方法。
ConcretePrototype:實(shí)現(xiàn)Prototype接口的類,這些類真正實(shí)現(xiàn)了克隆自身的功能。
Client:使用原型的客戶端,首先要獲取到原型實(shí)例對(duì)象,然后通過(guò)原型實(shí)例克隆自身來(lái)創(chuàng)建新的對(duì)象實(shí)例。
2.3 原型模式示例代碼##
- 先來(lái)看看原型接口的定義,示例代碼如下:
/**
* 聲明一個(gè)克隆自身的接口
*/
public interface Prototype {
/**
* 克隆自身的方法
* @return 一個(gè)從自身克隆出來(lái)的對(duì)象
*/
public Prototype clone();
}
- 接下來(lái)看看具體的原型實(shí)現(xiàn)對(duì)象,示例代碼如下:
/**
* 克隆的具體實(shí)現(xiàn)對(duì)象
*/
public class ConcretePrototype1 implements Prototype {
public Prototype clone() {
//最簡(jiǎn)單的克隆,新建一個(gè)自身對(duì)象,由于沒(méi)有屬性,就不去復(fù)制值了
Prototype prototype = new ConcretePrototype1();
return prototype;
}
}
/**
* 克隆的具體實(shí)現(xiàn)對(duì)象
*/
public class ConcretePrototype2 implements Prototype {
public Prototype clone() {
//最簡(jiǎn)單的克隆,新建一個(gè)自身對(duì)象,由于沒(méi)有屬性,就不去復(fù)制值了
Prototype prototype = new ConcretePrototype2();
return prototype;
}
}
為了跟上面原型模式的結(jié)構(gòu)示意圖保持一致,因此這兩個(gè)具體的原型實(shí)現(xiàn)對(duì)象,都沒(méi)有定義屬性。事實(shí)上,在實(shí)際使用原型模式的應(yīng)用中,原型對(duì)象多是有屬性的,克隆原型的時(shí)候也是需要克隆原型對(duì)象的屬性的,特此說(shuō)明一下。
- 再看看使用原型的客戶端,示例代碼如下:
/**
* 使用原型的客戶端
*/
public class Client {
/**
* 持有需要使用的原型接口對(duì)象
*/
private Prototype prototype;
/**
* 構(gòu)造方法,傳入需要使用的原型接口對(duì)象
* @param prototype 需要使用的原型接口對(duì)象
*/
public Client(Prototype prototype){
this.prototype = prototype;
}
/**
* 示意方法,執(zhí)行某個(gè)功能操作
*/
public void operation(){
//會(huì)需要?jiǎng)?chuàng)建原型接口的對(duì)象
Prototype newPrototype = prototype.clone();
}
}
2.4 使用原型模式重寫示例##
要使用原型模式來(lái)重寫示例,先要在訂單的接口上定義出克隆的接口,然后要求各個(gè)具體的訂單對(duì)象克隆自身,這樣就可以解決:在訂單處理對(duì)象里面通過(guò)訂單接口來(lái)創(chuàng)建新的訂單對(duì)象的問(wèn)題。
使用原型模式來(lái)重寫示例的結(jié)構(gòu)如圖所示:
- 復(fù)制誰(shuí)和誰(shuí)來(lái)復(fù)制的問(wèn)題
有了一個(gè)對(duì)象實(shí)例,要快速的創(chuàng)建跟它一樣的實(shí)例,最簡(jiǎn)單的辦法就是復(fù)制?這里又有兩個(gè)小的問(wèn)題:
復(fù)制誰(shuí)呢?當(dāng)然是復(fù)制這個(gè)對(duì)象實(shí)例,復(fù)制實(shí)例的意思是連帶著數(shù)據(jù)一起復(fù)制。
誰(shuí)來(lái)復(fù)制呢?應(yīng)該讓這個(gè)類的實(shí)例自己來(lái)復(fù)制,自己復(fù)制自己。
可是每個(gè)對(duì)象不會(huì)那么聽(tīng)話,自己去實(shí)現(xiàn)復(fù)制自己的。于是原型模式?jīng)Q定對(duì)這些對(duì)象實(shí)行強(qiáng)制要求,給這些對(duì)象定義一個(gè)接口,在接口里面定義一個(gè)方法,這個(gè)方法用來(lái)要求每個(gè)對(duì)象實(shí)現(xiàn)自己復(fù)制自己。
由于現(xiàn)在存在訂單的接口,因此就把這個(gè)要求克隆自身的方法定義在訂單的接口里面,示例代碼如下:
/**
* 訂單的接口,聲明了可以克隆自身的方法
*/
public interface OrderApi {
public int getOrderProductNum();
public void setOrderProductNum(int num);
/**
* 克隆方法
* @return 訂單原型的實(shí)例
*/
public OrderApi cloneOrder();
}
- 如何克隆
定義好了克隆的接口,那么在訂單的實(shí)現(xiàn)類里面,就得讓它實(shí)現(xiàn)這個(gè)接口,并具體的實(shí)現(xiàn)這個(gè)克隆方法,新的問(wèn)題出來(lái)了,如何實(shí)現(xiàn)克隆呢?
很簡(jiǎn)單,只要先new一個(gè)自己對(duì)象的實(shí)例,然后把自己實(shí)例中的數(shù)據(jù)取出來(lái),設(shè)置到新的對(duì)象實(shí)例中去,不就可以完成實(shí)例的復(fù)制了嘛,復(fù)制的結(jié)果就是有了一個(gè)跟自身一模一樣的實(shí)例。
/**
* 個(gè)人訂單對(duì)象
*/
public class PersonalOrder implements OrderApi{
private String customerName;
private String productId;
private int orderProductNum = 0;
public int getOrderProductNum() {
return this.orderProductNum;
}
public void setOrderProductNum(int num) {
this.orderProductNum = num;
}
public String getCustomerName() {
return customerName;
}
public void setCustomerName(String customerName) {
this.customerName = customerName;
}
public String getProductId() {
return productId;
}
public void setProductId(String productId) {
this.productId = productId;
}
public String toString(){
return "本個(gè)人訂單的訂購(gòu)人是="+this.customerName+",訂購(gòu)產(chǎn)品是="+this.productId+",訂購(gòu)數(shù)量為="+this.orderProductNum;
}
public OrderApi cloneOrder() {
//創(chuàng)建一個(gè)新的訂單,然后把本實(shí)例的數(shù)據(jù)復(fù)制過(guò)去
PersonalOrder order = new PersonalOrder();
order.setCustomerName(this.customerName);
order.setProductId(this.productId);
order.setOrderProductNum(this.orderProductNum);
return order;
}
}
接下來(lái)看看企業(yè)訂單的具體實(shí)現(xiàn),示例代碼如下:
/**
* 企業(yè)訂單對(duì)象
*/
public class EnterpriseOrder implements OrderApi{
private String enterpriseName;
private String productId;
private int orderProductNum = 0;
public int getOrderProductNum() {
return this.orderProductNum;
}
public void setOrderProductNum(int num) {
this.orderProductNum = num;
}
public String getEnterpriseName() {
return enterpriseName;
}
public void setEnterpriseName(String enterpriseName) {
this.enterpriseName = enterpriseName;
}
public String getProductId() {
return productId;
}
public void setProductId(String productId) {
this.productId = productId;
}
public String toString(){
return "本企業(yè)訂單的訂購(gòu)企業(yè)是="+this.enterpriseName+",訂購(gòu)產(chǎn)品是="+this.productId+",訂購(gòu)數(shù)量為="+this.orderProductNum;
}
public OrderApi cloneOrder() {
//創(chuàng)建一個(gè)新的訂單,然后把本實(shí)例的數(shù)據(jù)復(fù)制過(guò)去
EnterpriseOrder order = new EnterpriseOrder();
order.setEnterpriseName(this.enterpriseName);
order.setProductId(this.productId);
order.setOrderProductNum(this.orderProductNum);
return order;
}
}
- 使用克隆方法
這里使用訂單接口的克隆方法的,是訂單的處理對(duì)象,也就是說(shuō),訂單的處理對(duì)象就相當(dāng)于原型模式結(jié)構(gòu)中的Client。
當(dāng)然,客戶端在調(diào)用clone方法之前,還需要先獲得相應(yīng)的實(shí)例對(duì)象,有了實(shí)例對(duì)象,才能調(diào)用該實(shí)例對(duì)象的clone方法。
這里使用克隆方法的時(shí)候,跟標(biāo)準(zhǔn)的原型實(shí)現(xiàn)有一些不同,在標(biāo)準(zhǔn)的原型實(shí)現(xiàn)的示例代碼里面,客戶端是持有需要克隆的對(duì)象,而這里變化成了通過(guò)方法傳入需要使用克隆的對(duì)象,這點(diǎn)大家注意一下。示例代碼如下:
public class OrderBusiness {
/**
* 創(chuàng)建訂單的方法
* @param order 訂單的接口對(duì)象
*/
public void saveOrder(OrderApi order){
//1:判斷當(dāng)前的預(yù)定產(chǎn)品數(shù)量是否大于1000
while(order.getOrderProductNum() > 1000){
//2:如果大于,還需要繼續(xù)拆分
//2.1再新建一份訂單,跟傳入的訂單除了數(shù)量不一樣外,其它都相同
OrderApi newOrder = order.cloneOrder();
//然后進(jìn)行賦值,產(chǎn)品數(shù)量為1000
newOrder.setOrderProductNum(1000);
//2.2原來(lái)的訂單保留,把數(shù)量設(shè)置成減少1000
order.setOrderProductNum(order.getOrderProductNum()-1000);
//然后是業(yè)務(wù)功能處理,省略了,打印輸出,看一下
System.out.println("拆分生成訂單=="+newOrder);
}
//3:不超過(guò),那就直接業(yè)務(wù)功能處理,省略了,打印輸出,看一下
System.out.println("訂單=="+order);
}
}
2.5 小提示##
看到這里,可能有些朋友會(huì)認(rèn)為:Java的Object里面本身就有clone方法,還用搞得這么麻煩嗎?
雖然Java里面有clone方法,上面這么做還是很有意義的,可以讓我們更好的、更完整的體會(huì)原型這個(gè)設(shè)計(jì)模式。當(dāng)然,后面會(huì)講述如何使用Java里面的clone方法來(lái)實(shí)現(xiàn)克隆,不要著急。
3 模式講解#
3.1 認(rèn)識(shí)原型模式##
- 原型模式的功能
原型模式的功能實(shí)際上包含兩個(gè)方面:
一個(gè)是通過(guò)克隆來(lái)創(chuàng)建新的對(duì)象實(shí)例;
另一個(gè)是為克隆出來(lái)的新的對(duì)象實(shí)例復(fù)制原型實(shí)例屬性的值;
原型模式要實(shí)現(xiàn)的主要功能就是:通過(guò)克隆來(lái)創(chuàng)建新的對(duì)象實(shí)例。一般來(lái)講,新創(chuàng)建出來(lái)的實(shí)例的數(shù)據(jù)是和原型實(shí)例一樣的。但是具體如何實(shí)現(xiàn)克隆,需要由程序自行實(shí)現(xiàn),原型模式并沒(méi)有統(tǒng)一的要求和實(shí)現(xiàn)算法。
- 原型與new
原型模式從某種意義上說(shuō),就像是new操作,在前面的例子實(shí)現(xiàn)中,克隆方法就是使用new來(lái)實(shí)現(xiàn)的,但請(qǐng)注意,只是“類似于new”而不是“就是new”。
克隆方法和new操作最明顯的不同就在于:
new一個(gè)對(duì)象實(shí)例,一般屬性是沒(méi)有值的,或者是只有默認(rèn)值;如果是克隆得到的一個(gè)實(shí)例,通常屬性是有值的,屬性的值就是原型對(duì)象實(shí)例在克隆的時(shí)候,原型對(duì)象實(shí)例的屬性的值。
- 原型實(shí)例和克隆的實(shí)例
原型實(shí)例和克隆出來(lái)的實(shí)例,本質(zhì)上是不同的實(shí)例,克隆完成后,它們之間是沒(méi)有關(guān)聯(lián)的,如果克隆完成后,克隆出來(lái)的實(shí)例的屬性的值發(fā)生了改變,是不會(huì)影響到原型實(shí)例的。下面寫個(gè)示例來(lái)測(cè)試一下,示例代碼如下:
public class Client {
public static void main(String[] args) {
//先創(chuàng)建原型實(shí)例
OrderApi oa1 = new PersonalOrder();
//設(shè)置原型實(shí)例的訂單數(shù)量的值
oa1.setOrderProductNum(100);
//為了簡(jiǎn)單,這里僅僅輸出數(shù)量
System.out.println("這是第一次獲取的對(duì)象實(shí)例==="+oa1.getOrderProductNum());
//通過(guò)克隆來(lái)獲取新的實(shí)例
OrderApi oa2 = (OrderApi)oa1.cloneOrder();
//修改它的數(shù)量
oa2.setOrderProductNum(80);
//輸出克隆出來(lái)的對(duì)象的值
System.out.println("輸出克隆出來(lái)的實(shí)例==="+oa2.getOrderProductNum());
//再次輸出原型實(shí)例的值
System.out.println("再次輸出原型實(shí)例==="+oa1.getOrderProductNum());
}
}
運(yùn)行一下,看看結(jié)果:
這是第一次獲取的對(duì)象實(shí)例===100
輸出克隆出來(lái)的實(shí)例===80
再次輸出原型實(shí)例===100
仔細(xì)觀察上面的結(jié)果,會(huì)發(fā)現(xiàn)原型實(shí)例和克隆出來(lái)的實(shí)例是完全獨(dú)立的,也就是它們指向不同的內(nèi)存空間。因?yàn)榭寺〕鰜?lái)的實(shí)例的值已經(jīng)被改變了,而原型實(shí)例的值還是原來(lái)的值,并沒(méi)有變化,這就說(shuō)明兩個(gè)實(shí)例是對(duì)應(yīng)的不同內(nèi)存空間。
- 原型模式的調(diào)用順序示意圖
3.2 Java中的克隆方法##
在Java語(yǔ)言中已經(jīng)提供了clone方法,定義在Object類中。關(guān)于Java中clone方法的知識(shí),這里不去贅述,下面看看怎么使用Java里面的克隆方法來(lái)實(shí)現(xiàn)原型模式。
需要克隆功能的類,只需要實(shí)現(xiàn)java.lang.Cloneable接口,這個(gè)接口沒(méi)有需要實(shí)現(xiàn)的方法,是一個(gè)標(biāo)識(shí)接口。因此在前面的實(shí)現(xiàn)中,把訂單接口中的克隆方法去掉,現(xiàn)在直接實(shí)現(xiàn)Java中的接口就好了。新的訂單接口實(shí)現(xiàn),示例代碼如下:
public interface OrderApi {
public int getOrderProductNum();
public void setOrderProductNum(int num);
// public OrderApi cloneOrder();
}
另外在具體的訂單實(shí)現(xiàn)對(duì)象里面,實(shí)現(xiàn)方式上會(huì)有一些改變,個(gè)人訂單和企業(yè)訂單的克隆實(shí)現(xiàn)是類似的,因此示范一個(gè)就好了,看看個(gè)人訂單的實(shí)現(xiàn)吧,示例代碼如下:
/**
* 個(gè)人訂單對(duì)象,利用Java的Clone功能
*/
public class PersonalOrder implements Cloneable , OrderApi {
private String customerName;
private String productId;
private int orderProductNum = 0;
public int getOrderProductNum() {
return this.orderProductNum;
}
public void setOrderProductNum(int num) {
this.orderProductNum = num;
}
public String getCustomerName() {
return customerName;
}
public void setCustomerName(String customerName) {
this.customerName = customerName;
}
public String getProductId() {
return productId;
}
public void setProductId(String productId) {
this.productId = productId;
}
public String toString(){
return "本個(gè)人訂單的訂購(gòu)人是="+this.customerName+",訂購(gòu)產(chǎn)品是="+this.productId+",訂購(gòu)數(shù)量為="+this.orderProductNum;
}
// public OrderApi cloneOrder() {
// 創(chuàng)建一個(gè)新的訂單,然后把本實(shí)例的數(shù)據(jù)復(fù)制過(guò)去
// PersonalOrder order = new PersonalOrder();
// order.setCustomerName(this.customerName);
// order.setProductId(this.productId);
// order.setOrderProductNum(this.orderProductNum);
// return order;
// }
public Object clone() {
//克隆方法的真正實(shí)現(xiàn),直接調(diào)用父類的克隆方法就可以了
Object obj = null;
try {
obj = super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return obj;
}
}
看起來(lái),比完全由自己實(shí)現(xiàn)原型模式要稍稍簡(jiǎn)單點(diǎn),是否好用呢?還是測(cè)試一下,看看效果。客戶端跟上一個(gè)示例相比,作了兩點(diǎn)修改:
一個(gè)是原來(lái)的“OrderApi oa1 = new PersonalOrder();”這句話,要修改成:“PersonalOrder oa1 = new PersonalOrder();”。原因是現(xiàn)在的接口上并沒(méi)有克隆的方法,因此需要修改成原型的類型;
另外一個(gè)是“通過(guò)克隆來(lái)獲取新的實(shí)例”的實(shí)現(xiàn),需要修改成使用原型來(lái)調(diào)用在Object里面定義的clone()方法了,不再是調(diào)用原來(lái)的cloneOrder()了。
看看測(cè)試用的代碼,示例代碼如下:
public class Client {
public static void main(String[] args) {
//先創(chuàng)建原型實(shí)例
PersonalOrder oa1 = new PersonalOrder();
//設(shè)置原型實(shí)例的訂單數(shù)量的值
oa1.setOrderProductNum(100);
System.out.println("這是第一次獲取的對(duì)象實(shí)例==="+oa1.getOrderProductNum());
//通過(guò)克隆來(lái)獲取新的實(shí)例
PersonalOrder oa2 = (PersonalOrder)oa1.clone();
oa2.setOrderProductNum(80);
System.out.println("輸出克隆出來(lái)的實(shí)例==="+oa2.getOrderProductNum());
//再次輸出原型實(shí)例的值
System.out.println("再次輸出原型實(shí)例==="+oa1.getOrderProductNum());
}
}
3.3 淺度克隆和深度克隆##
無(wú)論你是自己實(shí)現(xiàn)克隆方法,還是采用Java提供的克隆方法,都存在一個(gè)淺度克隆和深度克隆的問(wèn)題,那么什么是淺度克隆?什么是深度克隆呢?簡(jiǎn)單地解釋一下:
淺度克隆:只負(fù)責(zé)克隆按值傳遞的數(shù)據(jù)(比如:基本數(shù)據(jù)類型、String類型)
深度克隆:除了淺度克隆要克隆的值外,還負(fù)責(zé)克隆引用類型的數(shù)據(jù),基本上就是被克隆實(shí)例所有的屬性的數(shù)據(jù)都會(huì)被克隆出來(lái)。
深度克隆還有一個(gè)特點(diǎn),如果被克隆的對(duì)象里面的屬性數(shù)據(jù)是引用類型,也就是屬性的類型也是對(duì)象,那么需要一直遞歸的克隆下去。這也意味著,要想深度克隆成功,必須要整個(gè)克隆所涉及的對(duì)象都要正確實(shí)現(xiàn)克隆方法,如果其中有一個(gè)沒(méi)有正確實(shí)現(xiàn)克隆,那么就會(huì)導(dǎo)致克隆失敗。
在前面的例子中實(shí)現(xiàn)的克隆就是典型的淺度克隆,下面就來(lái)看看如何實(shí)現(xiàn)深度克隆。
- 自己實(shí)現(xiàn)原型的深度克隆
(1)要演示深度克隆,需要給訂單對(duì)象添加一個(gè)引用類型的屬性,這樣實(shí)現(xiàn)克隆過(guò)后,才能看出深度克隆的效果來(lái)。
那就定義一個(gè)產(chǎn)品對(duì)象,也需要讓它實(shí)現(xiàn)克隆的功能,產(chǎn)品對(duì)象實(shí)現(xiàn)的是一個(gè)淺度克隆。先來(lái)定義產(chǎn)品的原型接口,示例代碼如下:
/**
* 聲明一個(gè)克隆產(chǎn)品自身的接口
*/
public interface ProductPrototype {
/**
* 克隆產(chǎn)品自身的方法
* @return 一個(gè)從自身克隆出來(lái)的產(chǎn)品對(duì)象
*/
public ProductPrototype cloneProduct();
}
接下來(lái)看看具體的產(chǎn)品對(duì)象實(shí)現(xiàn),示例代碼如下:
/**
* 產(chǎn)品對(duì)象
*/
public class Product implements ProductPrototype{
/**
* 產(chǎn)品編號(hào)
*/
private String productId;
/**
* 產(chǎn)品名稱
*/
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getProductId() {
return productId;
}
public void setProductId(String productId) {
this.productId = productId;
}
public String toString(){
return "產(chǎn)品編號(hào)="+this.productId+",產(chǎn)品名稱="+this.name;
}
public ProductPrototype cloneProduct() {
//創(chuàng)建一個(gè)新的訂單,然后把本實(shí)例的數(shù)據(jù)復(fù)制過(guò)去
Product product = new Product();
product.setProductId(this.productId);
product.setName(this.name);
return product;
}
}
(2)訂單的具體實(shí)現(xiàn)上也需要改變一下,需要在其屬性上添加一個(gè)產(chǎn)品類型的屬性,然后也需要實(shí)現(xiàn)克隆方法,示例代碼如下:
public class PersonalOrder implements OrderApi{
private String customerName;
private int orderProductNum = 0;
/**
* 產(chǎn)品對(duì)象
*/
private Product product = null;
public int getOrderProductNum() {
return this.orderProductNum;
}
public void setOrderProductNum(int num) {
this.orderProductNum = num;
}
public String getCustomerName() {
return customerName;
}
public void setCustomerName(String customerName) {
this.customerName = customerName;
}
public Product getProduct() {
return product;
}
public void setProduct(Product product) {
this.product = product;
}
public String toString(){
//簡(jiǎn)單點(diǎn)輸出
return "訂購(gòu)產(chǎn)品是="+this.product.getName()+",訂購(gòu)數(shù)量為="+this.orderProductNum;
}
public OrderApi cloneOrder() {
//創(chuàng)建一個(gè)新的訂單,然后把本實(shí)例的數(shù)據(jù)復(fù)制過(guò)去
PersonalOrder order = new PersonalOrder();
order.setCustomerName(this.customerName);
order.setOrderProductNum(this.orderProductNum);
//對(duì)于對(duì)象類型的數(shù)據(jù),深度克隆的時(shí)候需要繼續(xù)調(diào)用這個(gè)對(duì)象的克隆方法
order.setProduct((Product)this.product.cloneProduct());
return order;
}
}
(3)寫個(gè)客戶端來(lái)測(cè)試看看,是否深度克隆成功,示例代碼如下:
public class Client {
public static void main(String[] args) {
//先創(chuàng)建原型實(shí)例
PersonalOrder oa1 = new PersonalOrder();
//設(shè)置原型實(shí)例的值
Product product = new Product();
product.setName("產(chǎn)品1");
oa1.setProduct(product);
oa1.setOrderProductNum(100);
System.out.println("這是第一次獲取的對(duì)象實(shí)例="+oa1);
//通過(guò)克隆來(lái)獲取新的實(shí)例
PersonalOrder oa2 = (PersonalOrder)oa1.cloneOrder();
//修改它的值
oa2.getProduct().setName("產(chǎn)品2");
oa2.setOrderProductNum(80);
//輸出克隆出來(lái)的對(duì)象的值
System.out.println("輸出克隆出來(lái)的實(shí)例="+oa2);
//再次輸出原型實(shí)例的值
System.out.println("再次輸出原型實(shí)例="+oa1);
}
}
(4)運(yùn)行結(jié)果如下,很明顯,我們自己做的深度克隆是成功的:
這是第一次獲取的對(duì)象實(shí)例=訂購(gòu)產(chǎn)品是=產(chǎn)品1,訂購(gòu)數(shù)量為=100
輸出克隆出來(lái)的實(shí)例=訂購(gòu)產(chǎn)品是=產(chǎn)品2,訂購(gòu)數(shù)量為=80
再次輸出原型實(shí)例=訂購(gòu)產(chǎn)品是=產(chǎn)品1,訂購(gòu)數(shù)量為=100
(5)小結(jié)
看來(lái)自己實(shí)現(xiàn)深度克隆也不是很復(fù)雜,但是比較麻煩,如果產(chǎn)品類里面又有屬性是引用類型的話,在產(chǎn)品類實(shí)現(xiàn)克隆方法的時(shí)候,又需要調(diào)用那個(gè)引用類型的克隆方法了,這樣一層一層調(diào)下去,如果中途有任何一個(gè)對(duì)象沒(méi)有正確實(shí)現(xiàn)深度克隆,那將會(huì)引起錯(cuò)誤,這也是深度克隆容易出錯(cuò)的原因。
- Java中的深度克隆
(1)產(chǎn)品類沒(méi)有太大的不同,主要是把實(shí)現(xiàn)的接口變成了Cloneable,這樣一來(lái),實(shí)現(xiàn)克隆的方法就不是cloneProduct,而是變成clone方法了;另外一個(gè)是克隆方法的實(shí)現(xiàn)變成了使用“super.clone();”了,示例代碼如下:
public class Product implements Cloneable{
private String productId;
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getProductId() {
return productId;
}
public void setProductId(String productId) {
this.productId = productId;
}
public String toString(){
return "產(chǎn)品編號(hào)="+this.productId+",產(chǎn)品名稱="+this.name;
}
public Object clone() {
Object obj = null;
try {
obj = super.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return obj;
}
}
(2)具體的訂單實(shí)現(xiàn)類,除了改變接口外,更重要的是在實(shí)現(xiàn)clone方法的時(shí)候,除了調(diào)用“super.clone();”外,必須顯示的調(diào)用引用類型屬性的clone方法,也就是產(chǎn)品的clone方法,示例代碼如下:
public class PersonalOrder implements Cloneable , OrderApi{
private String customerName;
private Product product = null;
private int orderProductNum = 0;
public int getOrderProductNum() {
return this.orderProductNum;
}
public void setOrderProductNum(int num) {
this.orderProductNum = num;
}
public String getCustomerName() {
return customerName;
}
public void setCustomerName(String customerName) {
this.customerName = customerName;
}
public Product getProduct() {
return product;
}
public void setProduct(Product product) {
this.product = product;
}
public String toString(){
//簡(jiǎn)單點(diǎn)輸出
return "訂購(gòu)產(chǎn)品是="+this.product.getName()+",訂購(gòu)數(shù)量為="+this.orderProductNum;
}
public Object clone(){
PersonalOrder obj=null;
try {
obj =(PersonalOrder)super.clone();
//下面這一句話不可少
obj.setProduct((Product)this.product.clone());
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return obj;
}
}
(3)特別強(qiáng)調(diào):不可缺少“obj.setProduct((Product)this.product.clone());”這句話。為什么呢?
原因在于調(diào)用super.clone()方法的時(shí)候,Java是先開(kāi)辟一塊內(nèi)存的空間,然后把實(shí)例對(duì)象的值原樣拷貝過(guò)去,對(duì)于基本數(shù)據(jù)類型這樣做是沒(méi)有問(wèn)題的,而屬性product是一個(gè)引用類型,把值拷貝過(guò)去的意思就是把對(duì)應(yīng)的內(nèi)存地址拷貝過(guò)去了,也就是說(shuō)克隆后的對(duì)象實(shí)例的product和原型對(duì)象實(shí)例的product指向的是同一塊內(nèi)存空間,是同一個(gè)產(chǎn)品實(shí)例。
因此要想正確的執(zhí)行深度拷貝,必須手工的對(duì)每一個(gè)引用類型的屬性進(jìn)行克隆,并重新設(shè)置,覆蓋掉super.clone()所拷貝的值。
3.4 原型管理器##
如果一個(gè)系統(tǒng)中原型的數(shù)目不固定,比如系統(tǒng)中的原型可以被動(dòng)態(tài)的創(chuàng)建和銷毀,那么就需要在系統(tǒng)中維護(hù)一個(gè)當(dāng)前可用的原型的注冊(cè)表,這個(gè)注冊(cè)表就被稱為原型管理器。
其實(shí)如果把原型當(dāng)成一個(gè)資源的話,原型管理器就相當(dāng)于一個(gè)資源管理器,在系統(tǒng)開(kāi)始運(yùn)行的時(shí)候初始化,然后運(yùn)行期間可以動(dòng)態(tài)的添加資源和銷毀資源。從這個(gè)角度看,原型管理器就可以相當(dāng)于一個(gè)緩存資源的實(shí)現(xiàn),只不過(guò)里面緩存和管理的是原型實(shí)例而已。
有了原型管理器過(guò)后,一般情況下,除了向原型管理器里面添加原型對(duì)象的時(shí)候是通過(guò)new來(lái)創(chuàng)造的對(duì)象,其余時(shí)候都是通過(guò)向原型管理器來(lái)請(qǐng)求原型實(shí)例,然后通過(guò)克隆方法來(lái)獲取新的對(duì)象實(shí)例,這就可以實(shí)現(xiàn)動(dòng)態(tài)管理、或者動(dòng)態(tài)切換具體的實(shí)現(xiàn)對(duì)象實(shí)例。
還是通過(guò)示例來(lái)說(shuō)明,如何實(shí)現(xiàn)原型管理器。
- 先定義原型的接口,非常簡(jiǎn)單,除了克隆方法,提供一個(gè)名稱的屬性,示例代碼如下:
public interface Prototype {
public Prototype clone();
public String getName();
public void setName(String name);
}
- 再來(lái)看看兩個(gè)具體的實(shí)現(xiàn),實(shí)現(xiàn)方式基本上是一樣的,分別看看。先看第一個(gè)原型的實(shí)現(xiàn),示例代碼如下:
public class ConcretePrototype1 implements Prototype {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Prototype clone() {
ConcretePrototype1 prototype = new ConcretePrototype1();
prototype.setName(this.name);
return prototype;
}
public String toString(){
return "Now in Prototype1,name="+name;
}
}
再看看第二個(gè)原型的實(shí)現(xiàn),示例代碼如下:
public class ConcretePrototype2 implements Prototype {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Prototype clone() {
ConcretePrototype2 prototype = new ConcretePrototype2();
prototype.setName(this.name);
return prototype;
}
public String toString(){
return "Now in Prototype2,name="+name;
}
}
- 接下來(lái)看看原型管理器的實(shí)現(xiàn)示意,示例代碼如下:
/**
* 原型管理器
*/
public class PrototypeManager {
/**
* 用來(lái)記錄原型的編號(hào)和原型實(shí)例的對(duì)應(yīng)關(guān)系
*/
private static Map<String,Prototype> map = new HashMap<String,Prototype>();
/**
* 私有化構(gòu)造方法,避免外部無(wú)謂的創(chuàng)建實(shí)例
*/
private PrototypeManager(){
//
}
/**
* 向原型管理器里面添加或是修改某個(gè)原型注冊(cè)
* @param prototypeId 原型編號(hào)
* @param prototype 原型實(shí)例
*/
public synchronized static void setPrototype(String prototypeId,Prototype prototype){
map.put(prototypeId, prototype);
}
/**
* 從原型管理器里面刪除某個(gè)原型注冊(cè)
* @param prototypeId 原型編號(hào)
*/
public synchronized static void removePrototype(String prototypeId){
map.remove(prototypeId);
}
/**
* 獲取某個(gè)原型編號(hào)對(duì)應(yīng)的原型實(shí)例
* @param prototypeId 原型編號(hào)
* @return 原型編號(hào)對(duì)應(yīng)的原型實(shí)例
* @throws Exception 如果原型編號(hào)對(duì)應(yīng)的原型實(shí)例不存在,報(bào)出例外
*/
public synchronized static Prototype getPrototype(String prototypeId)throws Exception{
Prototype prototype = map.get(prototypeId);
if(prototype == null){
throw new Exception("您希望獲取的原型還沒(méi)有注冊(cè)或已被銷毀");
}
return prototype;
}
}
大家會(huì)發(fā)現(xiàn),原型管理器是類似一個(gè)工具類的實(shí)現(xiàn)方式,而且對(duì)外的幾個(gè)方法都是加了同步的,這主要是因?yàn)槿绻诙嗑€程環(huán)境下使用這個(gè)原型管理器的話,那個(gè)map屬性很明顯就成了大家競(jìng)爭(zhēng)的資源,因此需要加上同步。
- 接下來(lái)看看客戶端,如何使用這個(gè)原型管理器,示例代碼如下:
public class Client {
public static void main(String[] args) {
try {
// 初始化原型管理器
Prototype p1 = new ConcretePrototype1();
PrototypeManager.setPrototype("Prototype1", p1);
// 獲取原型來(lái)創(chuàng)建對(duì)象
Prototype p3 = PrototypeManager.getPrototype("Prototype1").clone();
p3.setName("張三");
System.out.println("第一個(gè)實(shí)例:" + p3);
// 有人動(dòng)態(tài)的切換了實(shí)現(xiàn)
Prototype p2 = new ConcretePrototype2();
PrototypeManager.setPrototype("Prototype1", p2);
// 重新獲取原型來(lái)創(chuàng)建對(duì)象
Prototype p4 = PrototypeManager.getPrototype("Prototype1").clone();
p4.setName("李四");
System.out.println("第二個(gè)實(shí)例:" + p4);
// 有人注銷了這個(gè)原型
PrototypeManager.removePrototype("Prototype1");
// 再次獲取原型來(lái)創(chuàng)建對(duì)象
Prototype p5 = PrototypeManager.getPrototype("Prototype1").clone();
p5.setName("王五");
System.out.println("第三個(gè)實(shí)例:" + p5);
} catch (Exception err) {
System.err.println(err.getMessage());
}
}
}
運(yùn)行一下,看看結(jié)果,結(jié)果示例如下:
第一個(gè)實(shí)例:Now in Prototype1,name=張三
第二個(gè)實(shí)例:Now in Prototype2,name=李四
您希望獲取的原型還沒(méi)有注冊(cè)或已被銷毀
3.5 原型模式的優(yōu)缺點(diǎn)##
- 對(duì)客戶端隱藏具體的實(shí)現(xiàn)類型
原型模式的客戶端,只知道原型接口的類型,并不知道具體的實(shí)現(xiàn)類型,從而減少了客戶端對(duì)這些具體實(shí)現(xiàn)類型的依賴。
- 在運(yùn)行時(shí)動(dòng)態(tài)改變具體的實(shí)現(xiàn)類型
原型模式可以在運(yùn)行期間,由客戶來(lái)注冊(cè)符合原型接口的實(shí)現(xiàn)類型,也可以動(dòng)態(tài)的改變具體的實(shí)現(xiàn)類型,看起來(lái)接口沒(méi)有任何變化,但其實(shí)運(yùn)行的已經(jīng)是另外一個(gè)類實(shí)例了。因?yàn)榭寺∫粋€(gè)原型就類似于實(shí)例化一個(gè)類。
- 深度克隆方法實(shí)現(xiàn)會(huì)比較困難
原型模式最大的缺點(diǎn)就在于每個(gè)原型的子類都必須實(shí)現(xiàn)clone的操作,尤其在包含引用類型的對(duì)象時(shí),clone方法會(huì)比較麻煩,必須要能夠遞歸的讓所有的相關(guān)對(duì)象都要正確的實(shí)現(xiàn)克隆。
3.6 思考原型模式##
- 原型模式的本質(zhì)
原型模式的本質(zhì):克隆生成對(duì)象。
克隆是手段,目的還是生成新的對(duì)象實(shí)例。正是因?yàn)樵偷哪康氖菫榱松尚碌膶?duì)象實(shí)例,原型模式通常是被歸類為創(chuàng)建型的模式。
原型模式也可以用來(lái)解決“只知接口而不知實(shí)現(xiàn)的問(wèn)題”,使用原型模式,可以出現(xiàn)一種獨(dú)特的“接口造接口”的景象,這在面向接口編程中很有用。同樣的功能也可以考慮使用工廠來(lái)實(shí)現(xiàn)。
另外,原型模式的重心還是在創(chuàng)建新的對(duì)象實(shí)例,至于創(chuàng)建出來(lái)的對(duì)象,其屬性的值是否一定要和原型對(duì)象屬性的值完全一樣,這個(gè)并沒(méi)有強(qiáng)制規(guī)定,只不過(guò)在目前大多數(shù)實(shí)現(xiàn)中,克隆出來(lái)的對(duì)象和原型對(duì)象的屬性值是一樣的。
也就是說(shuō),可以通過(guò)克隆來(lái)創(chuàng)造值不一樣的實(shí)例,但是對(duì)象類型必須一樣。可以有部分甚至是全部的屬性的值不一樣,可以有選擇性的克隆,就當(dāng)是標(biāo)準(zhǔn)原型模式的一個(gè)變形使用吧。
- 何時(shí)選用原型模式
建議在如下情況中,選用原型模式:
如果一個(gè)系統(tǒng)想要獨(dú)立于它想要使用的對(duì)象時(shí),可以使用原型模式,讓系統(tǒng)只面向接口編程,在系統(tǒng)需要新的對(duì)象的時(shí)候,可以通過(guò)克隆原型來(lái)得到;
如果需要實(shí)例化的類是在運(yùn)行時(shí)刻動(dòng)態(tài)指定時(shí),可以使用原型模式,通過(guò)克隆原型來(lái)得到需要的實(shí)例;
3.7 相關(guān)模式##
- 原型模式和抽象工廠模式
功能上有些相似,都是用來(lái)獲取一個(gè)新的對(duì)象實(shí)例的。
不同之處在于,原型模式的著眼點(diǎn)是在如何創(chuàng)造出實(shí)例對(duì)象來(lái),最后選擇的方案是通過(guò)克隆;而抽象工廠模式的著眼點(diǎn)則在于如何來(lái)創(chuàng)造產(chǎn)品簇,至于具體如何創(chuàng)建出產(chǎn)品簇中的每個(gè)對(duì)象實(shí)例,抽象工廠模式不是很關(guān)注。
正是因?yàn)樗鼈兊年P(guān)注點(diǎn)不一樣,所以它們也可以配合使用,比如在抽象工廠模式里面,具體創(chuàng)建每一種產(chǎn)品的時(shí)候就可以使用該種產(chǎn)品的原型,也就是抽象工廠管產(chǎn)品簇,具體的每種產(chǎn)品怎么創(chuàng)建則可以選擇原型模式。
- 原型模式和生成器模式
這兩種模式可以配合使用。
生成器模式關(guān)注的是構(gòu)建的過(guò)程,而在構(gòu)建的過(guò)程中,很可能需要某個(gè)部件的實(shí)例,那么很自然地就可以應(yīng)用上原型模式,通過(guò)原型模式來(lái)得到部件的實(shí)例。