1. 總述
☞ 23 種設計模式——行為型設計模式(11種)
軟件設計模式使人們可以更加簡單方便復用成功的設計和體系結構,它通常包含以下幾個基本要素:模式名稱、別名、動機、問題、解決方案、效果、模式角色、合作關系、實現方法、實用性、已知應用、例程、模式擴展和相關模式等。
設計模式有兩種分類方法,一種根據模式的目的來分;另一種根據模式的作用來分。
1.1 根據模式的目的划分
根據模式是用來完成什么樣的工作來划分,這種方法可分為創建型模式、結構型模式、行為型模式3種。
1.1.1 創建型模式
用於描述“怎么創建對象”。它的主要特點是“將對象的創建與使用分離”。如,單例、原型、工廠方法、抽象工廠、建造者等5種創建型模式。
1.1.2 結構型模式
用於描述“如何將類或對象按某種布局組成更大的結構”。如,代理、適配器、橋接、裝飾、外觀、享元、組合等7種結構型模式。
1.1.3 行為型模式
用於描述“類或對象之間怎樣相互協作共同完成單個對象無法單獨完成的任務,以及怎樣分配職責”。如,模板方法、策略、命令、職責鏈、狀態、觀察者、中介者、迭代器、訪問者、備忘錄模式、解釋器等11中行為模式。
1.2 根據模式的作用划分
根據模式的主要用於類上還是主要用戶對象上來分,這種方式可分為類模式和對象模式兩種。
1.2.1 類模式
用於處理類與子類之間的關系,這些關系通過繼承來建立,是靜態的,在編譯時便確定下來了。如,工廠方法、(類)適配器、模板方法、解釋器等4種類模式。
1.2.2 對象模式
用戶處理對象之間關系的,這些關系可以通過組合或聚合來實現,在運行時刻是可以變化的,更具動態性。
范圍/目的 |
創建型模式 |
結構型模式 |
行為型模式 |
類模式 |
工廠方法 |
(類)適配器 |
模板方法 解釋器 |
對象模式 |
單例 原型 抽象工廠 建造者 |
代理 (對象)適配器 橋接 裝飾 外觀 享元 組合 |
策略 命令 職責鏈 狀態 觀察者 中介者 迭代器 訪問者 備忘錄 |
1.3 23種設計模式功能
前面說明了這23種設計模式的分類,下面是對各個模式的功能進行介紹。
1)單例(Singleton)模式
某個類只能生成一個實例,該類提供了一個全局訪問點,供外部獲取該實例,其拓展是有限多個實例。
2)原型(Prototype)模式
將一個對象作為原型,通過對其進行復制而克隆出多個和原型類似的新實例。
3)工廠方法(Factory Method)模式
定義一個用戶創建產品的接口,有子類決定生產什么產品。
4)抽象工廠(Abstract Factory)模式
提供一個創建產品族的接口,其每個子類可以生產一些列相關的產品。
5)建造者(Builder)模式
將一個復雜對象分解成多個相對簡單的部分,然后根據不同需要分別創建它們,最后構建成該復雜對象。
6)代理(Proxy)模式
為某個對象提供一種代理以控制對對象的訪問。即客戶端通過代理間接地訪問該對象,從而限制、增強或修改該對象的一些特征。
7)適配器(Adapter)模式
將一個類的接口轉換成客戶希望的另一個接口,使得原本由於接口不兼容而不能一起工作的那些類能一起工作。
8)橋接(Bridge)模式
將抽象與實現分離,使它們可以獨立變化。它是用組合關系代替繼承關系來實現,從而降低 抽象和實現這兩個可變維度的耦合度。
9)裝飾(Decorator)模式
動態的給對象增加一些職責,即增加其額外的功能。
10)外觀(Facade)模式
為多個復雜的子系統提供一個一致的接口,使這些子系統更加容易被訪問。
11)享元(Flyweight)模式
運用共享技術來有效地支持大量細粒度對象的復用。
12)組合(Composite)模式
將對象組合成樹狀層次結構,使用戶對單個對象和組合對象具有一致的訪問性。
13)模板方法(Tempplate Method)模式
定義一個操作中的算法骨架,而將算法的一些步驟延遲到子類中,使得子類可以不改變該算法結構的情況下重定義該算法的某些特點步驟。
14)策略(Strategy)模式
定義了一系列算法,並將每個算法封裝起來,使它們可以相互替換,且算法的改變不會影響算法的客戶。
15)命令(Command)模式
將一個請求封裝為一個對象,使發出請求的責任和執行請求的責任分割開。
16)職責鏈(Chain of Responsibility)模式
把請求從鏈中的一個對象傳到下一個對象,直到請求被響應為止。通過這個方式去除對象之間的耦合。
17)狀態(State)模式
允許一個對象在其內部狀態發生改變時改變其行為能力。
18)觀察者(Observer)模式
多個對象間存在一對多的關系,當一個對象發生改變時,把這種改變通知給其他多個對象,從而影響其它對象的行為。
19)中介者(Mediator)模式
定義一個中介對象來簡化原有對象之間的交互關系,降低系統中對象的耦合度,使原有對象之間不必戶互了解。
20)迭代器(Iterator)模式
提供一種方法來順序訪問聚合對象中的一系列數據,而不暴露聚合對象的內部表示。
21)訪問者(Visitor)模式
在不改變集合元素的前提下,為一個集合中的每個元素提供多種訪問方式,即每個元素有多個訪問者對象。
22)備忘錄(Memento)模式
在不破壞封裝性的前提下,獲取並保存一個對象的內部狀態,以便以后回復它。
23)解釋器(Interpreter)
提供如何定義語言的文法,以及對語言句子的解釋方法,即解釋器。
必須指出,這 23 種並設計模式不是孤立存在的,很多模式之間存在一定的關聯關系,在大的系統開發中常常同時使用多種設計模式。
2. 創建型設計模式
創建型設計模式的主要關注點是“怎么創建對象”,它的主要特點是“將對象的創建與使用分離”。這樣可以降低系統的耦合度,使用者不需要關注對象的創建細節,對象的創建有相關的工廠來完成。就像我們去商城購買商品時,不需要知道商品是怎么深處出來的一樣,因為它們由專業的廠商生產。
創建型模式分為以下幾種:
- 單例(Singleton)模式:某個類只能生成一個實例,該實例提供一個全局訪問店供外部獲取該對象,其擴展時有限多例模式。
- 原型(Prototype)模式:將一個對象作為原型,通過對其進行復制而克隆出多個和原型類型的新實例。
- 工廠方法(Factory Method)模式:定義一個用於創建產品的接口,有子類決定生產什么產品。
- 抽象工廠(Abstract Factory)模式:提供一個創建產品族的接口,其每個子類可以生產一些列相關的產品。
- 建造者(Builder)模式:將一個復雜對象分解成多個相對簡單的部分,然后根據不同需要分別創建它們,最后構建從復雜對象。
以上 5 種創建型模式,處理工廠方法模式屬於(類)創建型模式,其他的全部屬於(對象)創建模式。
2.1 單例模式
在有些系統中,為了節省內存資源、保證數據內容的一致性,對某些類要求只能創建一個實例,這就是所謂的單例模式。
單例(Singleton)模式的定義:指一個類只有一個實例,其該類能自行創建這個實例的一種模式。例如,Windows 中只能打開一個任務管理器,這樣可以避免因打開多個任務管理器窗口而造成內存資源的浪費,或出現各個窗口顯示的內容不一致等錯誤。
在計算機系統中,還有 Windows 的回收站、操作系統中的文件系統、多線程中的線程池、顯卡的驅動程序對象、打印機的后台處理服務、數據庫的連接池、網站的計數器、Web 應用的配置對象、應用程序中的對話框、系統中的緩存等常常被設計成單例。
單例模式有以下3個特點:
1)單例類只有一個對象;
2)該單例對象必須由單例類自行創建;
3)單例類對外提供一個訪問該單例的全局訪問店。
2.1.1 單例模式的結構與實現
單例模式是設計模式中最簡單的模式之一。通常,普通類的 結構函數是公有的,外部類可以通過“new 構造函數()”來生成多個實例。但是,如果將類的構造函數設為私有的,外部類就無法通過調用該類的構造函數,也就無法生成多個實例。這是該類自身必須定義一個靜態私有實例,並向外提供一個講台的公有函數用於創建或獲取靜態私有實例。
下面分析單例模式的實現。
單例模式有懶漢式和餓漢式兩種實現形式。
第一種:懶漢式
該模式的特點是類加載時沒有生成單例,只有當第一次調用 getInstance 方法才去創建單例。代碼如下:
1 public class LazySingleton { 2 private static volatile LazySingleton instance = null; //保證 instance 在所有線程中同步 3 private LazySingleton() {} //private 避免類在外部被實例化 4 public static synchronized LazySingleton getInstance() { 5 // getInstance 方法前加同步 6 if(instance == null) { 7 instance = new LazySingleton(); 8 } 9 return instance; 10 } 11 }
注意:如果編寫的是多線程程序,則不要刪除上例代碼中的關鍵字 volatile 和 synchronized,鄒澤將存在線程非安全的問題。如果不刪除這兩個關鍵字就保證線程安全,但是每次訪問時都要同步,會影響性能,且小號更多的資源,這是懶漢式單例的缺點。
第二種:餓漢式單例
該模式的特點是類一旦加載就創建一個單例,保證在調用 getInstance 方法之前單例已經存在了。
1 public class HungrySingleton { 2 private static final HungrySingleton instance = new HungrySingleton(); 3 private HungrySingleton(){} 4 public static HungrySingleton getInstance() { 5 return instance; 6 } 7 }
餓漢式單例在類 創建的同時就已經創建好一個靜態的對象供系統使用,以后不再改變,以后線程安全的,可以直接用於多線程而不會出現問題。
2.1.2 單例模式的應用場景
- 在應用場景中,某類只要求生成一個對象的時候,如一個班中的板子、每個人的身份證號等。
- 當對象需要被共享的場合。由於單例模式志雲與創建一個對象,共享該對象可以節省內存,並加快對象訪問速度。如 Web 中的配置對象、數據庫的連接池等。
- 當某類需要頻繁實例化,而創建的對象有頻繁被銷毀的時候,如多線程的線程池、網絡連接池等。
2.1.3 單例模式的擴展
單例模式可擴展為有效的多例(Multiton)模式,這種模式可以生成有限個實例並保存在 ArmyList 中,客戶需要時可隨機獲取,其結構如圖2-1所示。
圖2-1 有限的多例模式的結構圖
2.2 原型(Prototype)模式
在有些系統中,存在大量相同或相似對象的創建問題,如果用傳統的構造函方法創建對象,會比較復雜且耗時耗資源,用原型模式生成對象就很高效,就像孫悟空拔下猴毛輕輕一吹就變出很多孫悟空一樣簡單。
2.2.1 原型模式的定義與特點
原型模式的定義如下:用一個已經創建的實例作為原型,通過復制該原型對象來創建一個和原型相同或相似的新對象。在這里,原型實例指定了要創建的對象的種類。這種方式創建對象非常高效,根本無需指定對象創建的細節。例如,Windows 操作系統的安裝通常比較耗時,如果復制就快了很多。
2.2.2 原型模式的結構和實現
由於 Java 提供了對象的 clone() 方法,所以用 Java 實現原型模式很簡單。
(1)模式的結構
原型模式包含以下主要角色:
① 抽象原型類:規定了具體原型對象必須實現的接口。
② 具體實現類:實現抽象原型類的 clone() 方法,它使可被復制的對象。
③ 訪問類:使用具體原型類中的 clone() 方法來復制新對象。
(2)模式的實現
原型模式的克隆分為淺克隆和深克隆,Java 中的 Object 類提供了淺克隆的 clone() 方法,具體原型類只要實現 Cloneable 接口就可實現對象的淺克隆,這里的 Cloneable 接口就是抽象原型類。
其代碼如下:
1 // 具體原型類 2 class Realizetype implements Cloneable { 3 Realizetype() { 4 System.out.println("具體原型創建成功!"); 5 } 6 public Object clone() throws CloneNotSupportedException { 7 System.out.println("具體原型復制成功!"); 8 return (Realizetype)super.clone(); 9 } 10 }
1 // 原型模式的測試類 2 public class PrototypeTest { 3 public static void main(String[] args)throws CloneNotSupportedException { 4 Realizetype obj1=new Realizetype(); 5 Realizetype obj2=(Realizetype)obj1.clone(); 6 System.out.println("obj1==obj2?"+(obj1==obj2)); 7 } 8 }
程序運行結果如下:
具體原型創建成功!
具體原型復制成功!
obj1==obj2?false
2.2.3 原型模式的應用實例
分析:孫悟空拔下猴毛輕輕一吹就變出很多孫悟空,這里實際上是用到了原型模式。這里的孫悟空類 SunWukong 是具體原型類,而 Java 中的 Cloneable 接口是抽象原型類。
我們需要重寫 Cloneable 接口的 clone() 方法,擁有復制新的孫悟空。訪問類可以通過調用孫悟空的 clone() 方法復制多個孫悟空,並在框架窗體 JFrame 中顯示。
程序代碼如下:
1 class SunWukong extends JPanel implements Cloneable { 2 3 public SunWukong() { 4 JLabel l1 = new JLabel(new ImageIcon("src/Wukong.jpg")); 5 this.add(l1); 6 } 7 public Object clone() { 8 SunWukong w = null; 9 try { 10 w = (SunWukong)super.clone(); 11 } catch(CloneNotSupportedException e) { 12 System.out.println("拷貝悟空失敗!"); 13 } 14 return w; 15 } 16 }
用原型模式除了可以生成相同的對象,還可以生成相似的對象,如以下實例:
分析:同一個學校的“三好學生”阿精裝出獲獎人姓名不同,其他的都想吐,屬於相似對象的復制,同樣可以用原型模式創建,然后再做簡單的修改就可以了。
程序代碼如下:
1 // 獎狀類 2 class citation implements Cloneable { 3 String name; 4 String info; 5 String college; 6 citation(String name,String info,String college) { 7 this.name = name; 8 this.info = info; 9 this.college = college; 10 System.out.println("獎狀創建成功!"); 11 } 12 void setName(String name) { 13 this.name = name; 14 } 15 String getName() { 16 return(this.name); 17 } 18 void display() { 19 System.out.println(name+info+college); 20 } 21 public Object clone() throws CloneNotSupportedException { 22 System.out.println("獎狀拷貝成功!"); 23 return (citation)super.clone(); 24 } 25 }
1 public class ProtoTypeCitation { 2 public static void main(String[] args) throws CloneNotSupportedException { 3 citation obj1 = new citation("張三","同學:在2016學年第一學期中表現優秀,被評為三好學生。","韶關學院"); 4 obj1.display(); 5 citation obj2 = (citation) obj1.clone(); 6 obj2.setName("李四"); 7 obj2.display(); 8 } 9 }
程序運行結果如下:
獎狀創建成功!
張三同學:在2016學年第一學期中表現優秀,被評為三好學生。韶關學院
獎狀拷貝成功!
李四同學:在2016學年第一學期中表現優秀,被評為三好學生。韶關學院
2.2.4 原型模式的應用場景
原型模式通常適用於以下場景。
- 對象之間相同或相似,即只是個別的幾個屬性不同的時候,
- 對象的創建過程比較麻煩,但復制比較簡單的時候。
2.2.5 原型模式的擴展
原型模式可擴展為帶原型管理器的原型模式,它在原型模式的基礎上增加了一個原型管理器 PrototypeManager 類。該類用 HashMap 保存多個復制的原型,Client 類可以通過管理器的 get(String id) 方法從中獲取復制的原型。結構圖如圖2-2所示:
圖2-2 帶原型管理器的原型模式的結構圖
用帶原型管理器的原型模式來生成包含“圓”和“正方形”等圓形的原型,並計算其面積。分析:本實例中由於存在不同的圖形類,例如,“圓”和“正方形”,它們計算面積的方法不一樣,所以需要用一個原型管理器來管理它們,圖2-3所示的是其結構圖。
圖2-3 圖形生成器的結構圖
程序代碼如下:
1 interface Shape extends Cloneable { 2 public Object clone(); //拷貝 3 public void countArea(); //計算面積 4 }
1 class Circle implements Shape { 2 public Object clone() { 3 Circle w = null; 4 try { 5 w = (Circle)super.clone(); 6 } catch(CloneNotSupportedException e) { 7 System.out.println("拷貝圓失敗!"); 8 } 9 return w; 10 } 11 public void countArea() { 12 int r = 0; 13 System.out.print("這是一個圓,請輸入圓的半徑:"); 14 Scanner input = new Scanner(System.in); // 阻塞,等待用戶在命令行輸入數據回車確認 15 r = input.nextInt(); 16 System.out.println("該圓的面積=" + 3.1415 * r * r + "\n"); 17 } 18 }
1 class Square implements Shape { 2 public Object clone() { 3 Square b = null; 4 try { 5 b = (Square)super.clone(); 6 } catch(CloneNotSupportedException e) { 7 System.out.println("拷貝正方形失敗!"); 8 } 9 return b; 10 } 11 public void countArea() { 12 int a=0; 13 System.out.print("這是一個正方形,請輸入它的邊長:"); 14 Scanner input = new Scanner(System.in); 15 a = input.nextInt(); 16 System.out.println("該正方形的面積=" + a * a + "\n"); 17 } 18 }
1 class ProtoTypeManager { 2 private HashMap<String, Shape>ht=new HashMap<String,Shape>(); 3 public ProtoTypeManager() { 4 ht.put("Circle", new Circle()); 5 ht.put("Square", new Square()); 6 } 7 public void addshape(String key,Shape obj) { 8 ht.put(key,obj); 9 } 10 public Shape getShape(String key) { 11 Shape temp = ht.get(key); 12 return (Shape) temp.clone(); 13 } 14 }
1 public class ProtoTypeShape { 2 public static void main(String[] args) { 3 ProtoTypeManager pm = new ProtoTypeManager(); 4 Shape obj1 = (Circle)pm.getShape("Circle"); 5 obj1.countArea(); 6 Shape obj2 = (Shape)pm.getShape("Square"); 7 obj2.countArea(); 8 } 9 }
運行結果如下:
這是一個圓,請輸入圓的半徑:3 該圓的面積=28.2735 這是一個正方形,請輸入它的邊長:3 該正方形的面積=9
2.3 工廠方法(Factory Method)模式
在顯示生活中社會分工越來越細,越來越專業。各種產品有專門的工廠生成蟒蛇地告別自給自足的小農經濟時代,這大大縮短了產品的生產周期,提高了生產效率。同樣,在軟件開發中能否做到軟件對象的生產和使用相分離呢?能否在滿足“開閉原則”的前提現下,客戶隨意增刪或改變對軟件相關對象的使用呢?
2.3.1 工廠方法模式的定義與特點
工廠方法模式的定義:定義一個創建產品對象的工廠接口,將產品對象的實際創建工作推遲都具體工廠類中。當滿足創建型模式中所要求的“創建與使用相分離”的特點。
我們把被創建的對象成為“產品”,把創建產品的對象成為“工廠”。如果要創建的產品不多,只要一個工廠類就可以完成,這種模式鍵“簡單工廠模式”,它不屬於23種經典設計模式,它的缺點是增加新產品時會違背“開閉原則”。
本節介紹的“工廠方法模式”是對簡單工廠模式的進一步抽象畫,其好處是可以是系統自不修改原來代碼的情況下引進新的產品,即滿足開閉原則。
工廠方法模式的主要優點有:
- 用戶只需要指導具體工廠的名稱就可以得到所需要的產品,無需知道產品的具體創建過程;
- 在系統增加新的產品時只需要添加具體產品類和對應的具體工廠類,無需對原工廠進行任何修改,滿足開閉原則。
其缺點是:每增加一個產品就要增加一個具體產品類和一個對應的具體工廠類,這增加了系統的復雜度。
2.3.2 工廠方法模式的結構與實現
工廠方法模式由抽象工廠、具體工廠、抽象產品和具體產品等 4 個要素構成。
(1)模式的結構
工廠方法模式的主要角色如下:
1)抽象工廠(Abstract Factory):提供了創建產品的接口,調用者通過它訪問具體工廠的工廠方法 newProduct() 來創建產品;
2)具體工廠(Concrete Factory):主要是實現抽象工廠中的抽象方法,具體產品的創建;
3)抽象產品(Product):定義了產品的規范,描述了產品的主要特征和功能;
4)具體產品(Concrete Product):實現了抽象產品角色所定義的接口,有具體工廠來創建,它同具體工廠之間一一對應。
其結構圖如圖2-4所示:
圖2-4 工廠方法模式的結構圖
(2)模式的實現
根據圖2-4 寫出該模式下的代碼:
1 // 抽象產品:提供了產品的接口 2 interface Product { 3 public void show(); 4 }
1 //具體產品1:實現抽象產品中的抽象方法 2 class ConcreteProduct1 implements Product { 3 public void show() { 4 System.out.println("具體產品1顯示..."); 5 } 6 }
1 // 具體產品2:實現抽象產品中的抽象方法 2 class ConcreteProduct2 implements Product { 3 public void show() { 4 System.out.println("具體產品2顯示..."); 5 } 6 }
1 // 抽象工廠:提供了廠品的生成方法 2 interface AbstractFactory { 3 public Product newProduct(); 4 }
1 //具體產品2:實現抽象產品中的抽象方法 2 class ConcreteProduct2 implements Product { 3 public void show() { 4 System.out.println("具體產品2顯示..."); 5 } 6 }
1 // 抽象工廠:提供了廠品的生成方法 2 interface AbstractFactory { 3 public Product newProduct(); 4 }
1 //具體工廠1:實現了廠品的生成方法 2 class ConcreteFactory1 implements AbstractFactory { 3 public Product newProduct() { 4 System.out.println("具體工廠1生成-->具體產品1..."); 5 return new ConcreteProduct1(); 6 } 7 }
1 //具體工廠2:實現了產品的生成方法 2 class ConcreteFactory2 implements AbstractFactory { 3 public Product newProduct() { 4 System.out.println("具體工廠2生成-->具體產品2..."); 5 return new ConcreteProduct2(); 6 } 7 }
2.3.3 模式的應用實例
【例】用工廠方法模式設計畜牧場。
分析:有很多種類的畜牧場,如養馬場用於養馬,養牛場用於養牛,所以該實例用工廠方法模式比較適合。
對養馬場和養牛場等具體工廠類,只要定義一個生產動物的方法 newAnimal() 即可。其結構圖如圖2-5所示。
圖2-5 畜牧場結構圖
程序代碼如下
1 // 抽象產品:動物類 2 interface IAnimal { 3 public void show(); 4 }
1 // 具體產品:馬類 2 public class Horse implements IAnimal { 3 @Override 4 public void show() { 5 System.out.println("這是一匹馬!"); 6 } 7 }
// 具體產品:牛類 public class Cattle implements IAnimal { @Override public void show() { System.out.println("這是一頭牛!"); } }
1 //抽象工廠:畜牧場 2 interface IAnimalFarm { 3 public IAnimal newAnimal(); 4 }
1 //具體工廠:養馬場 2 class HorseFarm implements IAnimalFarm { 3 public IAnimal newAnimal() { 4 System.out.println("新馬出生!"); 5 return new Horse(); 6 } 7 }
1 //具體工廠:養牛場 2 class CattleFarm implements IAnimalFarm { 3 public IAnimal newAnimal() { 4 System.out.println("新牛出生!"); 5 return new Cattle(); 6 } 7 }
1 public class AnimalFarmTest { 2 3 public static void main(String[] args) { 4 try { 5 IAnimal a; 6 IAnimalFarm af = (IAnimalFarm) ReadXML.getObject(HorseFarm.class); 7 if (af != null) { 8 a = af.newAnimal(); 9 a.show(); 10 } 11 } catch (Exception e) { 12 System.out.println(e.getMessage()); 13 } 14 } 15 } 16 17 public class ReadXML { 18 public static Object getObject(Class<?> mClass) { 19 try { 20 return mClass.newInstance(); 21 } catch (InstantiationException e) { 22 e.printStackTrace(); 23 return null; 24 } catch (IllegalAccessException e) { 25 e.printStackTrace(); 26 return null; 27 } 28 } 29 }
程序的運行結果:
新馬出生!
這是一匹馬!
2.3.4 模式的應用場景
工廠方法模式通常適用於以下場景。
- 客戶只知道創建產品的工廠名,而不知道具體的產品名。如 TCL 電視工廠、海信電視工廠等。
- 創建對象的任務由多個具體工廠中的某一個完成,而抽象工廠只提供創建產品的接口。
- 客戶不關心創建產品的細節,只關心產品的品牌。
2.3.5 模式的擴展
當需要生成的產品不多且不會增加,一個具體工廠類就可以完成任務時,可刪除抽象工廠類。這時工廠方法模式將退化到簡單工廠模式,其結構圖如圖2-5所示:
圖2-5 簡單工廠模式的結構圖
2.4 抽象工廠(AbstractFactory)模式
上節介紹的工廠方法模式中考慮的是一類產品的生產,如畜牧場只養動物、電視機廠只生產電視機、計算機軟件學院只培養計算機軟件專業的學生等。
同種類稱為同等級,也就是說:工廠方法模式只考慮深處同等級的產品,但是在顯示生活中許多工廠是綜合型的工廠,能生產多等級(種類)的產品,如農場里既養動物又種植物,電器廠既生產電視機又生產洗衣機或空調,大學既有軟件專業又有生物專業等。
本節要介紹的抽象工廠模式將考慮多等級產品的生產,將同一個具體工廠所生產的位於不同等級的一組產品成為一個產品族,圖2-6所示的是海爾工廠和TCL工廠生產的電視機與空調對應的關系圖。
圖2-6 電器工廠的產品等級與產品族
2.4.1 模式的定義與特點
抽象工廠模式的定義:是一種為訪問類提供一個創建一組相關或相互依賴對象的接口,其訪問類無須指定所要產品的具體類就能得到同族的不同等級的產品的模式結構。
抽象工廠模式是工廠方法模式的升級版本,工廠方法模式只生產一個等級的產品,而抽象工廠模式可生產多個等級的產品。
使用抽象工廠模式一般要滿足以下條件:
- 系統中有多個產品族,每個具體工廠創建同一族但屬於不同等級結構的產品。
- 系統一次只可能消費其中某一族產品,即同族的產品一起使用。
抽象工廠模式除了具有工廠方法模式的優點外,其他主要優點如下:
- 可以在類的內部對產品族中相關聯的多等級產品共同管理,而不必專門引入多個新的類來進行管理。
- 當增加一個新的產品族時不需要修改原代碼,滿足開閉原則。
其缺點是:當產品族中需要增加一個新的產品時,所有的工廠類都需要進行修改。
2.4.2 模式的結構與實現
抽象工廠模式同工廠方法模式一樣,也是由抽象工廠、具體工廠、抽象產品和具體產品等 4 個要素構成,但抽象工廠中方法個數不同,抽象產品的個數也不同。現在我們來分析其基本結構和實現方法。
(1)模式的結構
抽象工廠模式的主要角色如下:
① 抽象工廠:提供了創建產品的接口,它包含多個創建產品的方法 newProduct(),可以創建多個不同等級的產品。
② 具體工廠:主要是實現抽象工廠中的多個抽象方法,完成具體產品的創建。
③ 抽象產品:定義產品的規范,描述了產品的主要特征和功能,抽象工廠模式有多個抽象產品。
④ 具體產品:實現了抽象產品角色所定義的接口,由具體工廠來創建,它同具體工廠之間是多對一的關系。
抽象工廠模式的結構圖如圖2-7 所示:
圖2-7 抽象工廠模式的結構圖
(2)模式的實現
從圖2-7 可以看出抽象工廠模式的結構同工廠方法模式的結構相似,不同的是其產品的種類不止一個,所以創建產品的方法也不止一個。下面給出抽象工廠和具體工廠的代碼。
① 抽象工廠:提供了產品的生成方法。
1 interface AbstractFactory { 2 public Product1 newProduct1(); 3 public Product2 newProduct2(); 4 }
② 具體工廠:實現了產品的生成方法。
1 class ConcreteFactory1 implements AbstractFactory { 2 public Product1 newProduct1() { 3 System.out.println("具體工廠 1 生成-->具體產品 11..."); 4 return new ConcreteProduct11(); 5 } 6 public Product2 newProduct2() { 7 System.out.println("具體工廠 1 生成-->具體產品 21..."); 8 return new ConcreteProduct21(); 9 } 10 }
2.4.3 模式的應用實例
例:用抽象工廠模式設計農場類。
分析:農場中除了畜牧場一樣可以養動物,還可以培養植物,如養馬、養牛、種菜、種水果等,所以本實例比簽名介紹的畜牧場類復雜,必須用抽象工廠模式來實現。
本例用抽象工廠模式來設計兩個農場,一個是韶關農場用於養牛和種菜,一個是上饒農場用於養馬和種水果,可以在以上兩個農場中定義一個生產動物的方法 newAnimal() 和一個培養的方法 newPlant()。其結構如圖2-8所示。
圖2-8 農場類的結構圖
程序代碼如下:
1 // 抽象產品:動物類 2 interface Animal { 3 public void show(); 4 }
1 // 具體產品:馬類 2 class Horse implements Animal { 3 public void show() { 4 System.out.println("這是一匹馬"); 5 } 6 }
// 具體產品:牛類 class Cattle implements Animal { public void show() { System.out.println("這是一頭牛"); } }
1 // 抽象產品:植物類 2 interface Plant { 3 public void show(); 4 }
1 // 具體產品:水果類 2 class Fruitage implements Plant { 3 public void show() { 4 System.out.println("這是一水果"); 5 } 6 }
1 // 具體產品:蔬菜類 2 class Vegetables implements Plant { 3 public void show() { 4 System.out.println("這是一蔬菜"); 5 } 6 }
1 // 抽象工廠:農場類 2 interface Farm { 3 public Animal newAnimal(); 4 public Plant newPlant(); 5 }
1 // 具體工廠:韶關農場類 2 class SGfarm implements Farm { 3 public Animal newAnimal() { 4 System.out.println("新牛出生!"); 5 return new Cattle(); 6 } 7 public Plant newPlant() { 8 System.out.println("蔬菜長成!"); 9 return new Vegetables(); 10 } 11 }
1 // 具體工廠:上饒農場類 2 class SRfarm implements Farm { 3 public Animal newAnimal() { 4 System.out.println("新馬出生!"); 5 return new Horse(); 6 } 7 public Plant newPlant() { 8 System.out.println("水果長成!"); 9 return new Fruitage(); 10 } 11 }
2.4.4 模式的應用場景
抽象工廠模式最早的應用是用於創建處於不同操作系統的視窗構件。如 Java 的 AWT 中的 Button 和 Text 等構件在 Windows 和 UNIX 中的本地實現是不同的。
抽象工廠模式通常適用於以下場景:
- 當需要創建的對象是一些列相互關聯或相互依賴的產品族時,如電器工廠中的電視機、洗衣機、空調等。
- 系統中有多個產品族,但每次只使用其中的某一族產品。如有人只喜歡穿某一個品牌的衣服和鞋。
- 系統中提供了產品的類庫,其所有產品的接口相同,客戶端不依賴產品實例的創建細節和內部結構。
2.4.5 模式的擴展
抽象工廠模式的擴展有一定的“開閉原則”傾斜性:
- 當增加一個新的產品族時只需要一個新的具體工廠,不需要修改原代碼,滿足開閉原則。
- 當產品族中需要增加一個新種類的產品時,則所以的工廠類都需要進行修改,不滿足開閉原則。
另一方面,當系統中只存在一個等級結構的產品時,抽象工廠模式將退化到工廠方法模式。
2.5 建造者(Builder)模式
在軟件開發過程中有時需要創建一個復雜對象,這個復雜對象通常由多個子部件按一定的步驟組成而已。例如,計算機有 CPU、主板、內存、硬盤、顯卡、機箱、顯示器、鍵盤、鼠標等部件組裝而成的,采購員不可能自己去組裝計算機,而是將計算機的配置要求告訴計算機銷售公司,計算機銷售公司安排技術人員去組裝計算機,然后再交給要買計算機的采購員。
生活中這樣的例子很多,如游戲中的不同角色,其性別、個性、能力、臉型、體型、服裝、發型等特征都有所差異;還有汽車中的方向盤、發動機、車架、輪胎等部件也多種多樣;每封電子郵件的發件人、收件人、主題、內容、附件等內容各不相同。
以上所有這些產品都是由多個部件構成的,各個部件可以靈活選擇,但其創建步驟都大同小異。這類產品的創建無法用前面介紹的工廠模式描述,只有創建者模式可以很好地描述該類產品的創建。
2.5.1 模式的定義與特點
建造者模式定義:指將一個復雜對象的構造與它的表示分離,使同樣的構建過程可以創建不同的表示,這樣的設計模式被稱為建造者。它使將一個復雜的對象分解為多個簡單的對象,然后一步步構建而成。它將變與不變相分離,即產品的組成部分是不變得,但每一部分是可以靈活選擇的。
該模式的主要優點如下:
- 各個具體建造者相互獨立,有利於系統的擴展。
- 客戶端不必知道產品內部組成的細節,便於口直細節風險。
其缺點如下:
- 產品的組成部分必須相同,這限制了其使用的范圍。
- 如果產品的內部變化負責,該模式會增加很多的建造者類。
建造者模式和工廠模式的關注點不同:建造者模式注重零部件的組裝過程,而工廠方法模式更注重零 部件的創建過程,但兩者可以結合使用。
2.5.2 模式的結構與實現
建造者模式由產品、抽象建造者、具體建造者、指揮者等4個要素構成,現在我們來分析其基本結構和實現方法。
(1)模式的結構
建造者模式的主要角色如下。
- 產品角色:它是包含多個組成部件的復雜對象,有具體建造者來創建其各個部件。
- 抽象建造者:它是一個包含創建產品各個子部件的抽象方法的接口,通常還包含一個返回復雜產品的方法 getResult()。
- 具體建造者:實現 Builder 接口,完成復雜產品的各個部件的具體創建方法。
- 指揮者:它調用建造者對象中的部件構造與裝配方法完成復雜對象的創建,在指揮者中不涉及具體產品的信息。
其結構圖如圖2-9所示:
圖2-9 建造者模式的結構圖
(2)模式的實現
圖2-9 給出了建造者模式的主要結構,其相關類的代碼如下。
① 產品角色:包含多個組成部件的復雜對象。
1 class Product { 2 private String partA; 3 private String partB; 4 private String partC; 5 public void setPartA(String partA) { 6 this.partA=partA; 7 } 8 public void setPartB(String partB) { 9 this.partB=partB; 10 } 11 public void setPartC(String partC) { 12 this.partC=partC; 13 } 14 public void show() { 15 //顯示產品的特性 16 } 17 }
② 抽象建造者:包含創建產品各個子部件的抽象方法。
1 abstract class Builder { 2 //創建產品對象 3 protected Product product=new Product(); 4 public abstract void buildPartA(); 5 public abstract void buildPartB(); 6 public abstract void buildPartC(); 7 //返回產品對象 8 public Product getResult() { 9 return product; 10 } 11 }
③ 具體建造者:實現了抽象建造者接口。
1 public class ConcreteBuilder extends Builder { 2 public void buildPartA() { 3 product.setPartA("建造 PartA"); 4 } 5 public void buildPartB() { 6 product.setPartA("建造 PartB"); 7 } 8 public void buildPartC() { 9 product.setPartA("建造 PartC"); 10 } 11 }
④ 指揮者:調用建造者的方法完成復雜對象的創建。
1 class Director { 2 private Builder builder; 3 public Director(Builder builder) { 4 this.builder=builder; 5 } 6 //產品構建與組裝方法 7 public Product construct() { 8 builder.buildPartA(); 9 builder.buildPartB(); 10 builder.buildPartC(); 11 return builder.getResult(); 12 } 13 }
⑤ 客戶類。
1 public class Client { 2 public static void main(String[] args) { 3 Builder builder = new ConcreteBuilder(); 4 Director director = new Director(builder); 5 Product product = director.construct(); 6 product.show(); 7 } 8 }
2.5.3 模式的應用實例
例:用建造者模式描述客廳裝修。
分析:客廳裝修是一個復雜的過程,它包含牆體的裝修、電視機的選擇、沙發的購買與布局等。客戶把裝修要求告訴項目經理,項目經理指揮裝修工人一步步裝修,最后完成整個客廳的裝修與布局,所以本實例用建造者模式實現比較適合。
這里客廳是產品,包含牆、電視和沙發等組成部分。具體裝修工人是具體建造者,它們負責裝修與牆、電視和沙發的布局。項目經理指揮者,它負責指揮裝修工人進行裝修。其類圖如圖2-10所示。
圖2-10 客廳裝修的結構圖
程序代碼如下:
1 // 產品:客廳 2 class Parlour { 3 private String wall; //牆 4 private String TV; //電視 5 private String sofa; //沙發 6 public void setWall(String wall) { 7 this.wall = wall; 8 } 9 public void setTV(String TV) { 10 this.TV = TV; 11 } 12 public void setSofa(String sofa) { 13 this.sofa = sofa; 14 } 15 public void show() { 16 System.out.println("開始裝修客廳"); 17 } 18 }
1 // 抽象建造者:裝修工人 2 abstract class Decorator { 3 // 創建產品對象 4 protected Parlour product = new Parlour(); 5 publicabstract void buildWall(); 6 publicabstract void buildTV(); 7 publicabstract void buildSofa(); 8 //返回產品對象 9 public Parlour getResult() { 10 return product; 11 } 12 }
1 // 具體建造者:具體裝修工人1 2 class ConcreteDecorator1 extends Decorator { 3 public void buildWall() { 4 product.setWall("w1"); 5 } 6 public void buildTV() { 7 product.setTV("TV1"); 8 } 9 public void buildSofa() { 10 product.setSofa("sf1"); 11 } 12 }
1 //具體建造者:具體裝修工人2 2 class ConcreteDecorator2 extends Decorator { 3 public void buildWall() { 4 product.setWall("w2"); 5 } 6 public void buildTV() { 7 product.setTV("TV2"); 8 } 9 public void buildSofa() { 10 product.setSofa("sf2"); 11 } 12 }
1 //指揮者:項目經理 2 class ProjectManager { 3 private Decorator builder; 4 public ProjectManager(Decorator builder) { 5 this.builder = builder; 6 } 7 //產品構建與組裝方法 8 public Parlour decorate() { 9 builder.buildWall(); 10 builder.buildTV(); 11 builder.buildSofa(); 12 return builder.getResult(); 13 } 14 }
2.5.4 模式的應用場景
建造者模式創建的是負責對象,其產品的各個部分經常面臨着劇烈的變化,但將它們組合在一起的算法卻相對穩定,所以它通常在以下場合使用。
- 創建的對象較復雜,由多個部件構成,各部件面臨着復雜的變化,但構件間的建造順序是穩定的。
- 創建復雜對象的算法獨立於該對象的組成部分以及它們的裝配方式,即產品的構建過程和最終的表示獨立的。
2.5.5 模式的擴展
建造者模式在應用過程中可以根據需要改變,如果創建的產品種類只有一種,只需要一個具體建造者,這時可以省略掉抽象建造者,甚至可以省略指揮者角色。