深入探索Factory模式與Prototype模式的異同 (轉)

worldblog發表於2007-12-13
深入探索Factory模式與Prototype模式的異同 (轉)[@more@]

  原型與工廠模式的定義,本文不想在這講太多,本文主要想在這講一下對原型模式的一些誤解--將原型模式等價於工廠模式;
 為什麼會產生這種誤導呢?其實也不是我們的錯,關鍵在於設計模式這本書以及網上的其它資料很喜歡將原型和工廠方法進行比較,從而導致我們誤解了原型引入的本質意義。按我的理解,原型引入的根本原因就是在於它可以利用一個原型(在這,我指的是例項,而非類),地生成一批和原型物件一樣的例項。舉個例子來說,你有一個類A的例項a (A a=new A()),現在你想生成一個和a一樣的例項b,那麼,按照原型的定義,你應該可以這樣做b=a.clone()。這樣,你就可以得到一個和a一模一樣的例項b(即a和部b的資料成員的值完全一樣)。
  上面是原型的一個簡單說明,那麼引入原型有什麼好處呢?按我的理解,就是在於:你如果要生成一大批很相像的類的例項時,省得每次去做重複的賦值工作。再舉個例子,如果你有一個類A,它有十個成員變數,現在你打算生成100個A的例項,而這些例項的變數值大部分相同(比如說七個相同),只有一小部分不一樣(比如說三個),那麼如果沒有,那麼你就得每次New一個A的對像,然後賦值,這樣,你要重複100次同樣的七個變數的賦值工作,顯然,這樣很麻煩。現在你有了原型,那麼問題就簡單了,你只要生成一個A的例項,再透過clone來生成其它的例項,然後再一一修改其它例項不同的地方。
  可能我這麼講,大家不信,那下面,再讓我們來看看中活生生的原型應用。
學過Java的人都知道,在Java中,有一個clone(),這個函式的功能,就是返回一個和當前它的物件一樣的例項。那麼Java中為什麼要引入這個函式呢?在<>一書中,作者如是解釋:
  如果,你要將一個物件的引用作為引數傳進去,但又不希望函式改變物件的值,那麼,你該怎麼辦?由於在Java中對物件沒有像C++那樣的Const修飾符,所以,為了實現這個功能,Java中引入了clone函式,使得你將物件的引用作為引數傳進函式時,這個函式可以呼叫該物件的Clone方法生成該物件的一份複製,從而達到不修改原物件的目的。
  我之所以用上面這麼多篇幅來講述原型本質,目的就在於希望各位不要像我一樣,把原型的功能與它的意義給混了,以致於當真正要使用原型來解決問題時,卻不知可以使用它。
 好了,上面說了原型的本質意義--至少我認為是這樣的。那為什麼很多資料喜歡將原型同工廠模式進行比較呢?不知是不是巧合,雖然原型引入的初衷是像我上面所說,但他實現起來,卻又完全可以達到工廠模式的郊果(後面,我會用程式碼實現可以用工廠模式實現的Mac,Win產品系列生成問題)。而且,用起來甚至比工廠模式更方便、靈活。對於工廠模式與原形模式在功能上的這點巧合,我想也許是因為本來工廠模式和原型模式都是建立型模式,這樣,他們的基本功能都能生成物件,因而使得原型模式在功能上可以代替工廠模式。對這兩種模式在功能上的相同點,員2001年第11期雜誌上有一篇”非魚“寫的文章,作者理解得非常巧妙,即:如果你將工廠模式的UMl圖對摺,你得到的就是Prototype原型的UML圖。有興趣比較這兩種模式的朋友,可以去參考這篇文章。
 接下來,讓我們在實現機制上來看看原型模式為什麼可以實現工廠模式的功能(本文只限於Java語言)。在Java中,對於原型的實現,其實根本不用我們做,在類中早就定義了一個clone函式,而這個函式,就使得我們可以動態地生成物件的當前複製。即然這樣,那麼讓我們來看看,如果要實現工廠模式的功能,我
們該如何使用原型模式為做到呢?
 工廠模式實現的生產產品的功能,關鍵是利用了繼承的特性。也就是說,你生成的產品,一定是由同一個抽象產品類派生出來的。所以,在工廠模式下,你如果要生成一類產品,就要引入一個抽像產品類,然後再由它派生出具體產品。同樣,在原型模式中,你完全可以同樣定義一個這樣的“抽象產品--具體產品”層次,再利用具體產品本身的clone功能來產生具體產品本身。從而達到實現工廠模式功能的目的。可能說到這,大家有點糊塗了。實際上,在原型模式中,每個具體產品就扮演了工廠模式裡的具體工廠的角色(為什麼會這樣,其實很簡單,因為,每個具體產品都具有生成
自己複製的功能?從這種意義上講,難道這不正是工廠的作用嗎?)。另外,要在Java中利用原形模式實現工廠模式的功能,則更為簡單,因為object已經為我們實現了clone函式,且對於clone方法,Java中預設是:如果A是父類且A實現了clone函式,B是A的子類,則B不用實現clone函式,它只要呼叫父類的clone函式,Java就會在執行時動態地為我們生成正確的B的物件。理解這點的關鍵在於,所有類實現的clone操作都是呼叫object的clone方法。這也就是說,我上面所說的父類A根本就不用自己實現clone方法,而僅僅是呼叫父類(object)的clone方法而已。好,到了這,讀者也許又有疑問了,既然所有的cloen操作都是由object實現的,而在java中所有的自定義類預設都是由object派生而來,那這樣的話,應該所有的類都自動就具有了clone自己的能力?
 確實,如果object不將它的clone函式宣告為protect的話,情況的確如此。但Java為了方面的原因,所以沒有將clone方法公開,而是宣告為保護型別,這樣的話,子類是不可以直接呼叫object類的clone方法的,而必須做到如下兩點:
  1.必須實現Cloneable介面;
  2.必須宣告一個clone方法,來呼叫object的clone函式;
  Java在呼叫父類的clone函式時,都會在執行時動態地進行檢查,如果發現呼叫的類不符合上面的任何一點,則會丟擲一個異常。
 明白了上面的原因,那麼如果我們希望某個類具備clone自身的能力,那麼,我們可以這樣做:
  1.直接按上面所說,自己實現clone操作;
  2.宣告一個抽象父類,實現上面的clone操作並將它宣告為公開方法,再由此類派生出子類,這樣,所有的  子類只要呼叫父類的clone方法,就能夠正確地複製自己。
 通常,我們都是使用第一種方式,但在我們現在討論的如何用原型模式實現工廠模式的功能的問題中,我們最好是採用第二種方式。
 最後,讓我們透過具體的程式碼來看看如何用Prototype模式實現工廠模式的功能。

問題:
  現有兩類產品 1-Ram,2--,現在要生成具體的產品
  MacRam,MacCpu和WinRam,WinCpu.

程式碼如下:
/**
 *A:Abstract
 *C:Concrete
 */
 
/** 定義抽象產品Ram的類 APrototypeRam 
  * 同時他也是抽象工廠
  */

abstract class APrototypeRam implements Cloneable {
 public Object clone() {
  Object o=null;
  try {
  o=super.clone();//呼叫父類,即Object的clone()
  }
  catch(CloneNotSupportedException e) {
  System.err.println("APrototypeRam is not cloneable!");
  }
  return o;
 }
}

/** 定義抽象產品Ram的類APrototypeProductCpu
  * 同時他也是抽象工廠
  */

abstract class APrototypeCpu implements Cloneable {
 public Object  clone() {
  Object o=null;
  try {
  o=super.clone();//呼叫父類,即Object的clone()
  }
  catch(CloneNotSupportedException e) {
  System.err.println("APrototypeCpu is not cloneable!");
  }
  return o;
 }
}

/** 定義具體產品MacRam的類CPrototypeMacRam
  * 同時他也是具體工廠
  */

class CPrototypeMacRam extends APrototypeRam{
 public String toString() {
  return "MacRam";
 }
}

/** 定義具體產品WinRam的類CPrototypeWinRam
  * 同時他也是具體工廠
  */

class CPrototypeWinRam extends APrototypeRam {
 public String toString() {
  return "WinRam";
 }
}

/** 定義具體產品MacCpu的類CPrototypeMacCpu
  * 同時他也是具體工廠
  */

class CPrototypeMacCpu extends APrototypeCpu{
 public String toString() {
  return "MacCpu";
 }
}

/** 定義具體產品WinCpu的類CPrototypeWinCpu
  * 同時他也是具體工廠
  */

class CPrototypeWinCpu extends APrototypeCpu{
 public String toString() {
  return "WinCpu";
 }
}

/** 客戶端,使用CPrototypeRam和CPrototypeCpu生成如下產品
  * MacRam,MacCpu,WinRam,WinCpu
  */
 
public class Prototype {
 public static void main(String[] args) {
  /**
  * 在生成產品之前,先生成原型產品,以便後面利用它們成批生產相同產品
  * 其作用等價於產品工廠
  */
  CPrototypeMacRam prototypeMacRam=new CPrototypeMacRam();
  CPrototypeWinRam prototypeWinRam=new CPrototypeWinRam();
  CPrototypeMacCpu prototypeMacCpu=new CPrototypeMacCpu();
  CPrototypeWinCpu prototypeWinCpu=new CPrototypeWinCpu();
 
  CPrototypeMacRam MacRam=(CPrototypeMacRam)prototypeMacRam.clone();
  CPrototypeWinRam WinRam=(CPrototypeWinRam)prototypeWinRam.clone();
  CPrototypeMacCpu MacCpu=(CPrototypeMacCpu)prototypeMacCpu.clone();
  CPrototypeWinCpu WinCpu=(CPrototypeWinCpu)prototypeWinCpu.clone();
  System.out.println("列印原形產品與它的克隆產品與比較異同!");
  System.out.println("prototypeMacRam:"+prototypeMacRam+"  Cloned:"+MacRam);
  System.out.println("prototypeWinRam:"+prototypeWinRam+"  Cloned:"+WinRam);
  System.out.println("prototypeMacCpu:"+prototypeMacCpu+"  Cloned:"+MacCpu);
  System.out.println("prototypeWinCpu:"+prototypeWinCpu+"  Cloned:"+WinCpu);
 
 }
}

  透過上面程式碼,我們可以清楚地看到,用Prototype模式實現工廠模式更為簡單,如果再配上原型管理器的話,那麼Prototype模式則會變得更為靈活,限於篇幅,本文沒有講到原型管理器,有興趣的朋友可以參看後文列出的參考文獻。但同時,我們也發現,使用原形模式時,有一個不足之處,即在客戶端程式碼裡,我們必須顯示進行型別轉換,這樣可能導致錯誤。為了改正這一點,我想,我們可以使用真正的工廠模式將Prototype模式再封裝一遍。對工廠模式的這項功能,恐怕,Prototype原形模式就無能為力了。
  總之,工廠模式和原形模式雖然在引入目的上不同,但在實現上,原形模式可以實現工廠模式同樣的功能。但讀者也不要因為這樣,而將兩者混為一體,因為,反過來,在將原形模式作為生成本身複製的這項功能使用時,工廠模式根本無法取代它。 

參考文獻:

  1.設計模式--可複用物件導向的基礎  Gof
  2.Design Pattern In Java  James W. Cer
  3.Think In Java  Bruce Eckel
  4.程式設計師2001年第11期雜誌-理解設計模式(2)原型
 非魚


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10752043/viewspace-992890/,如需轉載,請註明出處,否則將追究法律責任。

相關文章