在OO設計領域,我們知道前人總結了不少的經驗,許多的經驗在現代軟件工程過程中已經被認為是原則來遵守。下面筆者摘抄幾項下文涉及到的OO原則的定義。

OCP(開閉原則,Open-Closed Principle):一個軟件的實體應當對擴展開放,對修改關閉。我的理解是,對于一個已有的軟件,如果需要擴展,應當在不需修改已有代碼的基礎上進行。

DIP(依賴倒轉原則,Dependence Inversion Principle):要針對接口編程,不要針對實現編程。我的理解是,對于不同層次的編程,高層次暴露給低層次的應當只是接口,而不是它的具體類。

LoD (迪米特法則,Law of Demeter):只與你直接的朋友通信,而避免和陌生人通信。眾所周知類(或模塊)之間的通信越少,耦合度就越低,從而更有利于我們對軟件的宏觀管理。 老子論“圣人之治”有相同的思想,《老子》云:“是以圣人之治,虛其心,實其腹,弱其志,常使民無知無欲。”,又云:“小國寡民,鄰國相望,雞犬之聲相 聞,民至老死,不相往來。”。佩服我們的老祖宗,N千年前就想到了西方N千年后才想到的東西,同時也佩服《java與模式》的作者閻宏,可以用中國傳統哲 學思想這么生動的說明這一軟件設計原則。

簡單工廠模式及實例

簡單工廠模式又叫靜態工廠模式,顧名思義,它是用來實例化目標類的靜態類。下面我主要通過一個簡單的實例說明簡單工廠及其優點。

比 如有個國家的運動員協會,他們是負責登記與注冊職業運動員的(就好像我們國家的體育總局,呵呵,無論足球籃球還是乒乓球的運動員都必須在這里注冊才能拿到 我們國家職業運動員牌照)。一家體育俱樂部(比如籃球的廣東宏遠,足球的深圳健力寶)想獲得球員為自己俱樂部效力,就必須通過這個運動員協會。

       根據DIP我們可以設計一個“運動員”接口,“足球運動員”和“籃球運動員”(還有其他運動員)都實現“運動員”這個接口。而“運動員協會”就是一個簡單工 廠類,它負責實例化“運動員”。我們這里的“俱樂部”就是一個客戶端(Client),不同的“俱樂部”就是不同的客戶端。

       對于不同的俱樂部對象(無論是八一還是深圳健力寶),他們都是面向“運動員”接口編程,而不用管是“足球運動員”還是“籃球運動員”,也就是說實現了“運動 員”接口的具體類“足球運動員”無需暴露給客戶端。這也滿足了DIP。但具體的俱樂部(比如足球的深圳健力寶)如何確保自己獲取的是自己想要的運動員(健 力寶俱樂部需要的當然是足球運動員)呢?這就需要“運動員協會”這一工廠類了。俱樂部通過調用“運動員協會”的具體方法,返回不同的實例。這同時也滿足了 LoD,也就是“深圳健力寶足球俱樂部”對象不直接與“足球運動員:李毅”對象通信,而是通過他們共同的“朋友”——“國家體育總局”通信。

下面給出各個類的程序,會有助于讀者更好的了解筆者之前的介紹。

    Code: [Copy to clipboard]  
運動員.java

 

public interface 運動員 {        

          public void 跑();

          public void 跳();

}

足球運動員.java

public class 足球運動員 implements 運動員 {

          public void 跑(){

                  //跑啊跑

          }

        

          public void 跳(){

                  //跳啊跳

          }

}

籃球運動員.java

public class 籃球運動員 implements 運動員 {

          public void 跑(){

                  //do nothing

          }

        

          public void 跳(){

                  //do nothing

          }

}

體育協會.java

public class 體育協會 {

        

          public static 運動員 注冊足球運動員(){

                  return new 足球運動員();

          }

        

          public static 運動員 注冊籃球運動員(){

                  return new 籃球運動員();

          }

}

俱樂部.java

public class 俱樂部 {

          private 運動員 守門員;

          private 運動員 后衛;

          private 運動員 前鋒;

          public void test() {

                  this.前鋒 = 體育協會.注冊足球運動員();

                  this.后衛 = 體育協會.注冊足球運動員();

                  this.守門員 = 體育協會.注冊足球運動員();

                

                  守門員.跑();

                  后衛.跳();

          }

}

以上就是簡單工廠模式的一個簡單實例,讀者應該想象不用接口不用工廠而把具體類暴露給客戶端的那種混亂情形吧(就好像沒了體育總局,各個俱樂部在市場上自己胡亂的尋找仔細需要的運動員),簡單工廠就解決了這種混亂。

我 們用OCP看看簡單工廠,會發現如果要對系統進行擴展的話治需要增加實現產品接口的產品類(上例表現為“足球運動員”,“籃球運動員”類,比如要增加個 “乒乓球運動員”類),而無需對原有的產品類進行修改。這咋一看好像滿足OCP,但是實際上還是需要修改代碼的——對,就是修改工廠類。上例中如果增加 “乒乓球運動員”產品類,就必須相應的修改“體育協會”工廠類,增加個“注冊乒乓球運動員”方法。所以可以看出,簡單工廠模式是不滿足OCP的。

工廠方法模式及其實例

談 了簡單工廠模式,下面繼續談談工廠方法模式。前一節的最末點明了簡單工廠模式最大的缺點——不完全滿足OCP。為了解決這一缺點,設計師們提出了工廠方法 模式。工廠方法模式和簡單工廠模式最大的不同在于,簡單工廠模式只有一個(對于一個項目或者一個獨立模塊而言)工廠類,而工廠方法模式有一組實現了相同接 口的工廠類。下面我們通過修改上一節的實例來介紹工廠方法模式。

我們在不改變產品類(“足球運動員”類和“籃球運動員”類)的情況下,修改下工廠類的結構,如下圖所示:

相關代碼如下:

    Code: [Copy to clipboard]  
運動員.java

 

public interface 運動員 {        

          public void 跑();

          public void 跳();

}

足球運動員.java

public class 足球運動員 implements 運動員 {

          public void 跑(){

                  //跑啊跑

          }

        

          public void 跳(){

                  //跳啊跳

          }

}

籃球運動員.java

public class 籃球運動員 implements 運動員 {

          public void 跑(){

                  //do nothing

          }

        

          public void 跳(){

                  //do nothing

          }

}

體育協會.java

public interface 體育協會 {

          public 運動員 注冊();

}

足球協會.java

public class 足球協會 implements 體育協會 {

          public 運動員 注冊(){

                  return new 足球運動員();

          }

}

籃球協會.java

public class 籃球協會 implements 體育協會 {

          public 運動員 注冊(){

                  return new 籃球運動員();

          }

}

俱樂部.java

public class 俱樂部 {

          private 運動員 守門員;

          private 運動員 后衛;

          private 運動員 前鋒;

          public void test() {

                  體育協會 中國足協 = new 足球協會();

                

                  this.前鋒 = 中國足協.注冊();

                  this.后衛 = 中國足協.注冊();

                  守門員.跑();

                  后衛.跳();

          }

}

很明顯可以看到,“體育協會”工廠類變成了“體育協會”接口,而實現此接口的分別是“足球協會”“籃球協會”等等具體的工廠類。

這樣做有什么好處呢?很明顯,這樣做就完全OCP了。如果需要再加入(或擴展)產品類(比如加多個“乒乓球運動員”)的話就不再需要修改工廠類了,而只需相應的再添加一個實現了工廠接口(“體育協會”接口)的具體工廠類。

從以上對兩種模式的介紹可以了解到,工廠方法模式是為了克服簡單工廠模式的缺點(主要是為了滿足OCP)而設計出來的。但是,工廠方法模式就一定比簡單工廠模式好呢?筆者的答案是不一定。下面筆者將詳細比較兩種模式。

1. 結構復雜度

從這個角度比較,顯然簡單工廠模式要占優。簡單工廠模式只需一個工廠類,而工廠方法模式的工廠類隨著產品類個數增加而增加,這無疑會使類的個數越來越多,從而增加了結構的復雜程度。

2.代碼復雜度

代碼復雜度和結構復雜度是一對矛盾,既然簡單工廠模式在結構方面相對簡潔,那么它在代碼方面肯定是比工廠方法模式復雜的了。簡單工廠模式的工廠類隨著產品類的增加需要增加很多方法(或代碼),而工廠方法模式每個具體工廠類只完成單一任務,代碼簡潔。

3.客戶端編程難度

工廠方法模式雖然在工廠類結構中引入了接口從而滿足了OCP,但是在客戶端編碼中需要對工廠類進行實例化。而簡單工廠模式的工廠類是個靜態類,在客戶端無需實例化,這無疑是個吸引人的優點。

4.管理上的難度

這是個關鍵的問題。

我 們先談擴展。眾所周知,工廠方法模式完全滿足OCP,即它有非常良好的擴展性。那是否就說明了簡單工廠模式就沒有擴展性呢?答案是否定的。簡單工廠模式同 樣具備良好的擴展性——擴展的時候僅需要修改少量的代碼(修改工廠類的代碼)就可以滿足擴展性的要求了。盡管這沒有完全滿足OCP,但筆者認為不需要太拘 泥于設計理論,要知道,sun提供的java官方工具包中也有想到多沒有滿足OCP的例子?。╦ava.util.Calendar這個抽象類就不滿足 OCP,具體原因大家可以分析下)。

然后我們從維護性的角度分析下。假如某個具體產品類需要進行一定的修改,很可能需要修改對應的工廠類。當同時 需要修改多個產品類的時候,對工廠類的修改會變得相當麻煩(對號入座已經是個問題了)。反而簡單工廠沒有這些麻煩,當多個產品類需要修改是,簡單工廠模式 仍然僅僅需要修改唯一的工廠類(無論怎樣都能改到滿足要求吧?大不了把這個類重寫)。

由以上的分析,筆者認為簡單工廠模式更好用更方便些。當然這只是筆者的個人看法而已,畢竟公認的,工廠方法模式比簡單工廠模式更“先進”。但有時過于先進的東西未必適合自己,這個見仁見智吧。



久久不醉