常見的java設計模式詳解
1. 根據目的來分
根據模式是用來完成什麼工作來劃分,這種方式可分爲創建型模式、結構型模式和行爲型模式 3 種。
創建型模式:用於描述“怎樣創建對象”,它的主要特點是“將對象的創建與使用分離”。GoF 中提供了單例、原型、工廠方法、抽象工廠、建造者等 5 種創建型模式。
結構型模式:用於描述如何將類或對象按某種佈局組成更大的結構,GoF 中提供了代理、適配器、橋接、裝飾、外觀、享元、組合等 7 種結構型模式。
行爲型模式:用於描述類或對象之間怎樣相互協作共同完成單個對象都無法單獨完成的任務,以及怎樣分配職責。GoF 中提供了模板方法、策略、命令、職責鏈、狀態、觀察者、中介者、迭代器、訪問者、備忘錄、解釋器等 11 種行爲型模式。
2 GoF的23種設計模式的功能
前面說明了 GoF 的 23 種設計模式的分類,現在對各個模式的功能進行介紹。
1 單例(Singleton)模式
某個類只能生成一個實例,該類提供了一個全局訪問點供外部獲取該實例,其拓展是有限多例模式。
2 原型(Prototype)模式
將一個對象作爲原型,通過對其進行復制而克隆出多個和原型類似的新實例。
3 工廠方法(Factory Method)模式
定義一個用於創建產品的接口,由子類決定生產什麼產品。
4 抽象工廠(AbstractFactory)模式
提供一個創建產品族的接口,其每個子類可以生產一系列相關的產品。
5 建造者(Builder)模式
將一個複雜對象分解成多個相對簡單的部分,然後根據不同需要分別創建它們,最後構建成該複雜對象。
6 代理(Proxy)模式
爲某對象提供一種代理以控制對該對象的訪問。即客戶端通過代理間接地訪問該對象,從而限制、增強或修改該對象的一些特性。
7適配器(Adapter)模式
將一個類的接口轉換成客戶希望的另外一個接口,使得原本由於接口不兼容而不能一起工作的那些類能一起工作。
8 橋接(Bridge)模式
將抽象與實現分離,使它們可以獨立變化。它是用組合關係代替繼承關係來實現,從而降低了抽象和實現這兩個可變維度的耦合度。
9 裝飾(Decorator)模式
動態的給對象增加一些職責,即增加其額外的功能。
10 外觀(Facade)模式
爲多個複雜的子系統提供一個一致的接口,使這些子系統更加容易被訪問。
11 享元(Flyweight)模式
運用共享技術來有效地支持大量細粒度對象的複用。
12 組合(Composite)模式
將對象組合成樹狀層次結構,使用戶對單個對象和組合對象具有一致的訪問性。
13 模板方法(TemplateMethod)模式
定義一個操作中的算法骨架,而將算法的一些步驟延遲到子類中,使得子類可以不改變該算法結構的情況下重定義該算法的某些特定步驟。
14 策略(Strategy)模式
定義了一系列算法,並將每個算法封裝起來,使它們可以相互替換,且算法的改變不會影響使用算法的客戶。
15 命令(Command)模式
將一個請求封裝爲一個對象,使發出請求的責任和執行請求的責任分割開。
16 職責鏈(Chain of Responsibility)模式
把請求從鏈中的一個對象傳到下一個對象,直到請求被響應爲止。通過這種方式去除對象之間的耦合。
17 狀態(State)模式
允許一個對象在其內部狀態發生改變時改變其行爲能力。
18 觀察者(Observer)模式
多個對象間存在一對多關係,當一個對象發生改變時,把這種改變通知給其他多個對象,從而影響其他對象的行爲。
19 中介者(Mediator)模式
定義一箇中介對象來簡化原有對象之間的交互關係,降低系統中對象間的耦合度,使原有對象之間不必相互瞭解。
20 迭代器(Iterator)模式
提供一種方法來順序訪問聚合對象中的一系列數據,而不暴露聚合對象的內部表示。
21 訪問者(Visitor)模式
在不改變集合元素的前提下,爲一個集合中的每個元素提供多種訪問方式,即每個元素有多個訪問者對象訪問。
22 備忘錄(Memento)模式
在不破壞封裝性的前提下,獲取並保存一個對象的內部狀態,以便以後恢復它。
23 解釋器(Interpreter)模式
提供如何定義語言的文法,以及對語言句子的解釋方法,即解釋器。
注:這 23 種設計模式不是孤立存在的,很多模式之間存在一定的關聯關係,在大的系統開發中常常同時使用多種設計模式
3.下面介紹幾種常見的模式
單例(Singleton)模式
前言
在有些系統中,爲了節省內存資源、保證數據內容的一致性,對某些類要求只能創建一個實例,這就是所謂的單例模式。
1)單例(Singleton)模式的定義
指一個類只有一個實例,且該類能自行創建這個實例的一種模式。例如,Windows 中只能打開一個任務管理器,這樣可以避免因打開多個任務管理器窗口而造成內存資源的浪費,或出現各個窗口顯示內容的不一致等錯誤。
2)特點
- 單例類只有一個實例對象;
- 該單例對象必須由單例類自行創建;
- 單例類對外提供一個訪問該單例的全局訪問點;
3)實現
有兩種有懶漢式單例和餓漢式單例實現方式
1》第一種 懶漢式單例
public class LazySingleton
{
private static volatile LazySingleton instance=null; //保證 instance 在所有線程中同步
private LazySingleton(){} //private 避免類在外部被實例化
public static synchronized LazySingleton getInstance()
{
//getInstance 方法前加同步
if(instance==null)
{
instance=new LazySingleton();
}
return instance;
}
}
注意:如果編寫的是多線程程序,則不要刪除上例代碼中的關鍵字 volatile 和 synchronized,否則將存在線程非安全的問題。如果不刪除這兩個關鍵字就能保證線程安全,但是每次訪問時都要同步,會影響性能,且消耗更多的資源,這是懶漢式單例的缺點。
2》第二種 餓漢式單例
public class HungrySingleton
{
private static final HungrySingleton instance=new HungrySingleton();
private HungrySingleton(){}
public static HungrySingleton getInstance()
{
return instance;
}
}
餓漢式單例在類創建的同時就已經創建好一個靜態的對象供系統使用,以後不再改變,所以是線程安全的,可以直接用於多線程而不會出現問題。
4)適用場景特點
在應用場景中,某類只要求生成一個對象的時候,如一個班中的班長、每個人的身份證號等。
當對象需要被共享的場合。由於單例模式只允許創建一個對象,共享該對象可以節省內存,並加快對象訪問速度。如 Web 中的配置對象、數據庫的連接池等。
當某類需要頻繁實例化,而創建的對象又頻繁被銷燬的時候,如多線程的線程池、網絡連接池等。
5)擴展
單例模式可擴展爲有限的多例(Multitcm)模式,這種模式可生成有限個實例並保存在 ArmyList 中,客戶需要時可隨機獲取。
工廠方法模式
前言
在現實生活中社會分工越來越細,越來越專業化。各種產品有專門的工廠生產,徹底告別了自給自足的小農經濟時代,這大大縮短了產品的生產週期,提高了生產效率。同樣,在軟件開發中能否做到軟件對象的生產和使用相分離呢?能否在滿足“開閉原則”的前提下,客戶隨意增刪或改變對軟件相關對象的使用呢?這就是本節要討論的問題。
1)工廠方法模式的定義
工廠方法(FactoryMethod)模式的定義:定義一個創建產品對象的工廠接口,將產品對象的實際創建工作推遲到具體子工廠類當中。這滿足創建型模式中所要求的“創建與使用相分離”的特點。
我們把被創建的對象稱爲“產品”,把創建產品的對象稱爲“工廠”。如果要創建的產品不多,只要一個工廠類就可以完成,這種模式叫“簡單工廠模式”,它不屬於 GoF 的 23 種經典設計模式,它的缺點是增加新產品時會違背“開閉原則”。
下面介紹的“工廠方法模式”是對簡單工廠模式的進一步抽象化,其好處是可以使系統在不修改原來代碼的情況下引進新的產品,即滿足開閉原則。
2)特點
工廠方法模式的主要優點有:
用戶只需要知道具體工廠的名稱就可得到所要的產品,無須知道產品的具體創建過程;
在系統增加新的產品時只需要添加具體產品類和對應的具體工廠類,無須對原工廠進行任何修改,滿足開閉原則;
其缺點是:每增加一個產品就要增加一個具體產品類和一個對應的具體工廠類,這增加了系統的複雜度。
3)實現
工廠方法模式的主要角色如下。
-
抽象工廠(Abstract Factory):提供了創建產品的接口,調用者通過它訪問具體工廠的工廠方法 newProduct()來創建產品。
-
具體工廠(ConcreteFactory):主要是實現抽象工廠中的抽象方法,完成具體產品的創建。
-
抽象產品(Product):定義了產品的規範,描述了產品的主要特性和功能。
-
具體產品(ConcreteProduct):實現了抽象產品角色所定義的接口,由具體工廠來創建,它同具體工廠之間一一對應。
package FactoryMethod;
//測試類
public class AbstractFactoryTest
{
public static void main(String[] args)
{
try
{
Product a;
AbstractFactory af;
//這裏可以使用配置文件的方式獲取那個工廠
//af=(AbstractFactory) ReadXML1.getObject();
//爲了測試方便 我們指定一個工廠
af = new ConcreteFactory1();
a=af.newProduct();
a.show();
}
catch(Exception e)
{
System.out.println(e.getMessage());
}
}
}
//抽象產品:提供了產品的接口
interface Product
{
public void show();
}
//具體產品1:實現抽象產品中的抽象方法
class ConcreteProduct1 implements Product
{
public void show()
{
System.out.println("具體產品1顯示...");
}
}
//具體產品2:實現抽象產品中的抽象方法
class ConcreteProduct2 implements Product
{
public void show()
{
System.out.println("具體產品2顯示...");
}
}
//抽象工廠:提供了廠品的生成方法
interface AbstractFactory
{
public Product newProduct();
}
//具體工廠1:實現了廠品的生成方法
class ConcreteFactory1 implements AbstractFactory
{
public Product newProduct()
{
System.out.println("具體工廠1生成-->具體產品1...");
return new ConcreteProduct1();
}
}
//具體工廠2:實現了廠品的生成方法
class ConcreteFactory2 implements AbstractFactory
{
public Product newProduct()
{
System.out.println("具體工廠2生成-->具體產品2...");
return new ConcreteProduct2();
}
}
4)適用場景特點
- 客戶只知道創建產品的工廠名,而不知道具體的產品名。如 TCL 電視工廠、海信電視工廠等。
- 創建對象的任務由多個具體子工廠中的某一個完成,而抽象工廠只提供創建產品的接口。
- 客戶不關心創建產品的細節,只關心產品的品牌。
5)擴展
當需要生成的產品不多且不會增加,一個具體工廠類就可以完成任務時,可刪除抽象工廠類。這時工廠方法模式將退化到簡單工廠模式
抽象工廠模式
前言
前面介紹的工廠方法模式中考慮的是一類產品的生產,如畜牧場只養動物、電視機廠只生產電視機、計算機軟件學院只培養計算機軟件專業的學生等。
同種類稱爲同等級,也就是說:工廠方法模式只考慮生產同等級的產品,但是在現實生活中許多工廠是綜合型的工廠,能生產多等級(種類) 的產品,如農場裏既養動物又種植物,電器廠既生產電視機又生產洗衣機或空調,大學既有軟件專業又有生物專業等。
本節要介紹的抽象工廠模式將考慮多等級產品的生產,將同一個具體工廠所生產的位於不同等級的一組產品稱爲一個產品族,圖 1 所示的是海爾工廠和 TCL 工廠所生產的電視機與空調對應的關係圖。
1)抽象工廠(AbstractFactory)模式
是一種爲訪問類提供一個創建一組相關或相互依賴對象的接口,且訪問類無須指定所要產品的具體類就能得到同族(指這個工廠生產的東西,例如 海爾工廠 即生產電視 也生產冰箱,海爾電視和海爾冰箱都屬於同族)的不同等級的產品的模式結構。
2)特點
使用抽象工廠模式一般要滿足以下條件。
- 系統中有多個產品族,每個具體工廠創建同一族但屬於不同等級結構的產品。
- 系統一次只可能消費其中某一族產品,即同族的產品一起使用。
抽象工廠模式除了具有工廠方法模式的優點外,其他主要優點如下。
可以在類的內部對產品族中相關聯的多等級產品共同管理,而不必專門引入多個新的類來進行管理。
當增加一個新的產品族時不需要修改原代碼,滿足開閉原則。
其缺點是:當產品族中需要增加一個新的產品時,所有的工廠類都需要進行 修改。
3)實現
抽象工廠模式同工廠方法模式一樣,也是由抽象工廠、具體工廠、抽象產品和具體產品等 4 個要素構成,但抽象工廠中方法個數不同,抽象產品的個數也不同。現在我們來分析其基本結構和實現方法。
抽象工廠模式的主要角色如下。
- 抽象工廠(Abstract Factory):提供了創建產品的接口,它包含多個創建產品的方法newProduct(),可以創建多個不同等級的產品。
- 具體工廠(Concrete Factory):主要是實現抽象工廠中的多個抽象方法,完成具體產品的創建。
- 抽象產品(Product):定義了產品的規範,描述了產品的主要特性和功能,抽象工廠模式有多個抽象產品。
- 具體產品(ConcreteProduct):實現了抽象產品角色所定義的接口,由具體工廠來創建,它 同具體工廠之間是多對一的關係。
(1) 抽象工廠:提供了產品的生成方法
interface AbstractFactory
{
public Product1 newProduct1();
public Product2 newProduct2();
}
(2) 具體工廠:實現了產品的生成方法。
class ConcreteFactory1 implements AbstractFactory
{
public Product1 newProduct1()
{
System.out.println("具體工廠 1 生成-->具體產品 11...");
return new ConcreteProduct11();
}
public Product2 newProduct2()
{
System.out.println("具體工廠 1 生成-->具體產品 21...");
return new ConcreteProduct21();
}
}
4)適用場景特點
當需要創建的對象是一系列相互關聯或相互依賴的產品族時,如電器工廠中的電視機、洗衣機、空調等。
系統中有多個產品族,但每次只使用其中的某一族產品。如有人只喜歡穿某一個品牌的衣服和鞋。
系統中提供了產品的類庫,且所有產品的接口相同,客戶端不依賴產品實例的創建細節和內部結構。
5)擴展
抽象工廠模式的擴展有一定的“開閉原則”傾斜性:
當增加一個新的產品族時只需增加一個新的具體工廠,不需要修改原代碼,滿足開閉原則。
當產品族中需要增加一個新種類的產品時,則所有的工廠類都需要進行修改,不滿足開閉原則。
另一方面,當系統中只存在一個等級結構的產品時,抽象工廠模式將退化到工廠方法模式。
策略(Strategy)模式
前言
在現實生活中常常遇到實現某種目標存在多種策略可供選擇的情況,例如,出行旅遊可以乘坐飛機、乘坐火車、騎自行車或自己開私家車等,超市促銷可以釆用打折、送商品、送積分等方法。
在軟件開發中也常常遇到類似的情況,當實現某一個功能存在多種算法或者策略,我們可以根據環境或者條件的不同選擇不同的算法或者策略來完成該功能,如數據排序策略有冒泡排序、選擇排序、插入排序、二叉樹排序等。
如果使用多重條件轉移語句實現(即硬編碼),不但使條件語句變得很複雜,而且增加、刪除或更換算法要修改原代碼,不易維護,違背開閉原則。如果採用策略模式就能很好解決該問題。
1)策略(Strategy)模式的定義
該模式定義了一系列算法,並將每個算法封裝起來,使它們可以相互替換,且算法的變化不會影響使用算法的客戶。策略模式屬於對象行爲模式,它通過對算法進行封裝,把使用算法的責任和算法的實現分割開來,並委派給不同的對象對這些算法進行管理。
2)特點
策略模式的主要優點如下。
- 多重條件語句不易維護,而使用策略模式可以避免使用多重條件語句。
- 策略模式提供了一系列的可供重用的算法族,恰當使用繼承可以把算法族的公共代碼轉移到父類裏面,從而避免重複的代碼。
- 策略模式可以提供相同行爲的不同實現,客戶可以根據不同時間或空間要求選擇不同的。
- 策略模式提供了對開閉原則的完美支持,可以在不修改原代碼的情況下,靈活增加新算法。
- 策略模式把算法的使用放到環境類中,而算法的實現移到具體策略類中,實現了二者的分離。
其主要缺點如下。
- 客戶端必須理解所有策略算法的區別,以便適時選擇恰當的算法類。
- 策略模式造成很多的策略類。
3)實現
策略模式是準備一組算法,並將這組算法封裝到一系列的策略類裏面,作爲一個抽象策略類的子類。策略模式的重心不是如何實現算法,而是如何組織這些算法,從而讓程序結構更加靈活,具有更好的維護性和擴展性,現在我們來分析其基本結構和實現方法。
1. 模式的結構
策略模式的主要角色如下。
-
抽象策略(Strategy)類:定義了一個公共接口,各種不同的算法以不同的方式實現這個接口,環境角色使用這個接口調用不同的算法,一般使用接口或抽象類實現。
-
具體策略(Concrete Strategy)類:實現了抽象策略定義的接口,提供具體的算法實現。
環境(Context)類:持有一個策略類的引用,最終給客戶端調用。package strategy; public class StrategyPattern { public static void main(String[] args) { Context c=new Context(); Strategy s=new ConcreteStrategyA(); c.setStrategy(s); c.strategyMethod(); System.out.println("-----------------"); s=new ConcreteStrategyB(); c.setStrategy(s); c.strategyMethod(); } } //抽象策略類 interface Strategy { public void strategyMethod(); //策略方法 } //具體策略類A class ConcreteStrategyA implements Strategy { public void strategyMethod() { System.out.println("具體策略A的策略方法被訪問!"); } } //具體策略類B class ConcreteStrategyB implements Strategy { public void strategyMethod() { System.out.println("具體策略B的策略方法被訪問!"); } } //環境類 class Context { private Strategy strategy; public Strategy getStrategy() { return strategy; } public void setStrategy(Strategy strategy) { this.strategy=strategy; } public void strategyMethod() { strategy.strategyMethod(); } }
4)適用場景特點
策略模式在很多地方用到,如 Java SE 中的容器佈局管理就是一個典型的實例,Java SE 中的每個容器都存在多種佈局供用戶選擇。在程序設計中,通常在以下幾種情況中使用策略模式較多。
一個系統需要動態地在幾種算法中選擇一種時,可將每個算法封裝到策略類中。
一個類定義了多種行爲,並且這些行爲在這個類的操作中以多個條件語句的形式出現,可將每個條件分支移入它們各自的策略類中以代替這些條件語句。
系統中各算法彼此完全獨立,且要求對客戶隱藏具體算法的實現細節時。
系統要求使用算法的客戶不應該知道其操作的數據時,可使用策略模式來隱藏與算法相關的數據結構。
多個類只區別在表現行爲不同,可以使用策略模式,在運行時動態選擇具體要執行的行爲。
5)擴展
在一個使用策略模式的系統中,當存在的策略很多時,客戶端管理所有策略算法將變得很複雜,如果在環境類中使用策略工廠模式來管理這些策略類將大大減少客戶端的工作複雜度
建造者(Builder)模式
前言
在軟件開發過程中有時需要創建一個複雜的對象,這個複雜對象通常由多個子部件按一定的步驟組合而成。例如,計算機是由 OPU、主板、內存、硬盤、顯卡、機箱、顯示器、鍵盤、鼠標等部件組裝而成的,採購員不可能自己去組裝計算機,而是將計算機的配置要求告訴計算機銷售公司,計算機銷售公司安排技術人員去組裝計算機,然後再交給要買計算機的採購員。
生活中這樣的例子很多,如遊戲中的不同角色,其性別、個性、能力、臉型、體型、服裝、髮型等特性都有所差異;還有汽車中的方向盤、發動機、車架、輪胎等部件也多種多樣;每封電子郵件的發件人、收件人、主題、內容、附件等內容也各不相同。
以上所有這些產品都是由多個部件構成的,各個部件可以靈活選擇,但其創建步驟都大同小異。這類產品的創建無法用前面介紹的工廠模式描述,只有建造者模式可以很好地描述該類產品的創建。
1)建造者(Builder)模式的定義
指將一個複雜對象的構造與它的表示分離,使同樣的構建過程可以創建不同的表示,這樣的設計模式被稱爲建造者模式。它是將一個複雜的對象分解爲多個簡單的對象,然後一步一步構建而成。它將變與不變相分離,即產品的組成部分是不變的,但每一部分是可以靈活選擇的。
2)特點
該模式的主要優點如下:
各個具體的建造者相互獨立,有利於系統的擴展。
客戶端不必知道產品內部組成的細節,便於控制細節風險。
其缺點如下:
產品的組成部分必須相同,這限制了其使用範圍。
如果產品的內部變化複雜,該模式會增加很多的建造者類。
建造者(Builder)模式和工廠模式的關注點不同:建造者模式注重零部件的組裝過程,而工廠方法模式更注重零部件的創建過程,但兩者可以結合使用。
3)實現
建造者(Builder)模式由產品、抽象建造者、具體建造者、指揮者等 4 個要素構成
建造者(Builder)模式的主要角色如下。
產品角色(Product):它是包含多個組成部件的複雜對象,由具體建造者來創建其各個滅部件。
抽象建造者(Builder):它是一個包含創建產品各個子部件的抽象方法的接口,通常還包含一個返回複雜產品的方法 getResult()。
具體建造者(Concrete Builder):實現 Builder 接口,完成複雜產品的各個部件的具體創建方法。
指揮者(Director):它調用建造者對象中的部件構造與裝配方法完成複雜對象的創建,在指揮者中不涉及具體產品的信息。
(1) 產品角色:包含多個組成部件的複雜對象。
class Product
{
private String partA;
private String partB;
private String partC;
public void setPartA(String partA)
{
this.partA=partA;
}
public void setPartB(String partB)
{
this.partB=partB;
}
public void setPartC(String partC)
{
this.partC=partC;
}
public void show()
{
//顯示產品的特性
}
}
(2) 抽象建造者:包含創建產品各個子部件的抽象方法。
abstract class Builder
{
//創建產品對象
protected Product product=new Product();
public abstract void buildPartA();
public abstract void buildPartB();
public abstract void buildPartC();
//返回產品對象
public Product getResult()
{
return product;
}
}
(3) 具體建造者:實現了抽象建造者接口。
public class ConcreteBuilder extends Builder
{
public void buildPartA()
{
product.setPartA("建造 PartA");
}
public void buildPartB()
{
product.setPartA("建造 PartB");
}
public void buildPartC()
{
product.setPartA("建造 PartC");
}
}
(4) 指揮者:調用建造者中的方法完成複雜對象的創建。
class Director
{
private Builder builder;
public Director(Builder builder)
{
this.builder=builder;
}
//產品構建與組裝方法
public Product construct()
{
builder.buildPartA();
builder.buildPartB();
builder.buildPartC();
return builder.getResult();
}
}
(5) 客戶類。
public class Client
{
public static void main(String[] args)
{
Builder builder=new ConcreteBuilder();
Director director=new Director(builder);
Product product=director.construct();
product.show();
}
}
4)適用場景特點
建造者(Builder)模式創建的是複雜對象,其產品的各個部分經常面臨着劇烈的變化,但將它們組合在一起的算法卻相對穩定,所以它通常在以下場合使用。
創建的對象較複雜,由多個部件構成,各部件面臨着複雜的變化,但構件間的建造順序是穩定的。
創建複雜對象的算法獨立於該對象的組成部分以及它們的裝配方式,即產品的構建過程和最終的表示是獨立的。
5)擴展
建造者(Builder)模式在應用過程中可以根據需要改變,如果創建的產品種類只有一種,只需要一個具體建造者,這時可以省略掉抽象建造者,甚至可以省略掉指揮者角色。
職責鏈(Chain of Responsibility)模式
前言
在現實生活中,常常會出現這樣的事例:一個請求有多個對象可以處理,但每個對象的處理條件或權限不同。例如,公司員工請假,可批假的領導有部門負責人、副總經理、總經理等,但每個領導能批准的天數不同,員工必須根據自己要請假的天數去找不同的領導簽名,也就是說員工必須記住每個領導的姓名、電話和地址等信息,這增加了難度。這樣的例子還有很多,如找領導出差報銷、生活中的“擊鼓傳花”遊戲等。
在計算機軟硬件中也有相關例子,如總線網中數據報傳送,每臺計算機根據目標地址是否同自己的地址相同來決定是否接收;還有異常處理中,處理程序根據異常的類型決定自己是否處理該異常;還有 Struts2 的攔截器、JSP 和 Servlet 的 Filter 等,所有這些,如果用責任鏈模式都能很好解決。
1)職責鏈(Chain of Responsibility)模式的定義
爲了避免請求發送者與多個請求處理者耦合在一起,將所有請求的處理者通過前一對象記住其下一個對象的引用而連成一條鏈;當有請求發生時,可將請求沿着這條鏈傳遞,直到有對象處理它爲止。
注意:責任鏈模式也叫職責鏈模式。
2)特點
在責任鏈模式中,客戶只需要將請求發送到責任鏈上即可,無須關心請求的處理細節和請求的傳遞過程,所以責任鏈將請求的發送者和請求的處理者解耦了。
責任鏈模式是一種對象行爲型模式,其主要優點如下。
降低了對象之間的耦合度。該模式使得一個對象無須知道到底是哪一個對象處理其請求以及鏈的結構,發送者和接收者也無須擁有對方的明確信息。
增強了系統的可擴展性。可以根據需要增加新的請求處理類,滿足開閉原則。
增強了給對象指派職責的靈活性。當工作流程發生變化,可以動態地改變鏈內的成員或者調動它們的次序,也可動態地新增或者刪除責任。
責任鏈簡化了對象之間的連接。每個對象只需保持一個指向其後繼者的引用,不需保持其他所有處理者的引用,這避免了使用衆多的 if 或者 if···else 語句。
責任分擔。每個類只需要處理自己該處理的工作,不該處理的傳遞給下一個對象完成,明確各類的責任範圍,符合類的單一職責原則。
其主要缺點如下。
不能保證每個請求一定被處理。由於一個請求沒有明確的接收者,所以不能保證它一定會被處理,該請求可能一直傳到鏈的末端都得不到處理。
對比較長的職責鏈,請求的處理可能涉及多個處理對象,系統性能將受到一定影響。
職責鏈建立的合理性要靠客戶端來保證,增加了客戶端的複雜性,可能會由於職責鏈的錯誤設置而導致系統出錯,如可能會造成循環調用。
3)實現
通常情況下,可以通過數據鏈表來實現職責鏈模式的數據結構。
職責鏈模式主要包含以下角色。
抽象處理者(Handler)角色:定義一個處理請求的接口,包含抽象處理方法和一個後繼連接。
具體處理者(Concrete Handler)角色:實現抽象處理者的處理方法,判斷能否處理本次請求,如果可以處理請求則處理,否則將該請求轉給它的後繼者。
客戶類(Client)角色:創建處理鏈,並向鏈頭的具體處理者對象提交請求,它不關心處理細節和請求的傳遞過程。
package chainOfResponsibility;
public class ChainOfResponsibilityPattern
{
public static void main(String[] args)
{
//組裝責任鏈
Handler handler1=new ConcreteHandler1();
Handler handler2=new ConcreteHandler2();
handler1.setNext(handler2);
//提交請求
handler1.handleRequest("two");
}
}
//抽象處理者角色
abstract class Handler
{
private Handler next;
public void setNext(Handler next)
{
this.next=next;
}
public Handler getNext()
{
return next;
}
//處理請求的方法
public abstract void handleRequest(String request);
}
//具體處理者角色1
class ConcreteHandler1 extends Handler
{
public void handleRequest(String request)
{
if(request.equals("one"))
{
System.out.println("具體處理者1負責處理該請求!");
}
else
{
if(getNext()!=null)
{
getNext().handleRequest(request);
}
else
{
System.out.println("沒有人處理該請求!");
}
}
}
}
//具體處理者角色2
class ConcreteHandler2 extends Handler
{
public void handleRequest(String request)
{
if(request.equals("two"))
{
System.out.println("具體處理者2負責處理該請求!");
}
else
{
if(getNext()!=null)
{
getNext().handleRequest(request);
}
else
{
System.out.println("沒有人處理該請求!");
}
}
}
}
4)適用場景特點
前邊已經講述了關於責任鏈模式的結構與特點,下面介紹其應用場景,責任鏈模式通常在以下幾種情況使用。
有多個對象可以處理一個請求,哪個對象處理該請求由運行時刻自動確定。
可動態指定一組對象處理請求,或添加新的處理者。
在不明確指定請求處理者的情況下,向多個處理者中的一個提交請求。
5)擴展
職責鏈模式存在以下兩種情況。
純的職責鏈模式:一個請求必須被某一個處理者對象所接收,且一個具體處理者對某個請求的處理只能採用以下兩種行爲之一:自己處理(承擔責任);把責任推給下家處理。
不純的職責鏈模式:允許出現某一個具體處理者對象在承擔了請求的一部分責任後又將剩餘的責任傳給下家的情況,且一個請求可以最終不被任何接收端對象所接收。
觀察者模式
前言
在現實世界中,許多對象並不是獨立存在的,其中一個對象的行爲發生改變可能會導致一個或者多個其他對象的行爲也發生改變。例如,某種商品的物價上漲時會導致部分商家高興,而消費者傷心;還有,當我們開車到交叉路口時,遇到紅燈會停,遇到綠燈會行。這樣的例子還有很多,例如,股票價格與股民、微信公衆號與微信用戶、氣象局的天氣預報與聽衆、小偷與警察等。
在軟件世界也是這樣,例如,Excel 中的數據與折線圖、餅狀圖、柱狀圖之間的關係;MVC 模式中的模型與視圖的關係;事件模型中的事件源與事件處理者。所有這些,如果用觀察者模式來實現就非常方便。
1)觀察者模式的定義
指多個對象間存在一對多的依賴關係,當一個對象的狀態發生改變時,所有依賴於它的對象都得到通知並被自動更新。這種模式有時又稱作發佈-訂閱模式、模型-視圖模式,它是對象行爲型模式。
2)特點
觀察者模式是一種對象行爲型模式,其主要優點如下。
降低了目標與觀察者之間的耦合關係,兩者之間是抽象耦合關係。
目標與觀察者之間建立了一套觸發機制。
它的主要缺點如下。
目標與觀察者之間的依賴關係並沒有完全解除,而且有可能出現循環引用。
當觀察者對象很多時,通知的發佈會花費很多時間,影響程序的效率。
3)實現
實現觀察者模式時要注意具體目標對象和具體觀察者對象之間不能直接調用,否則將使兩者之間緊密耦合起來,這違反了面向對象的設計原則。
- 模式的結構
觀察者模式的主要角色如下。
抽象主題(Subject)角色:也叫抽象目標類,它提供了一個用於保存觀察者對象的聚集類和增加、刪除觀察者對象的方法,以及通知所有觀察者的抽象方法。
具體主題(Concrete Subject)角色:也叫具體目標類,它實現抽象目標中的通知方法,當具體主題的內部狀態發生改變時,通知所有註冊過的觀察者對象。
抽象觀察者(Observer)角色:它是一個抽象類或接口,它包含了一個更新自己的抽象方法,當接到具體主題的更改通知時被調用。
具體觀察者(Concrete Observer)角色:實現抽象觀察者中定義的抽象方法,以便在得到目標的更改通知時更新自身的狀態。
package observer;
import java.util.*;
public class ObserverPattern
{
public static void main(String[] args)
{
Subject subject=new ConcreteSubject();
Observer obs1=new ConcreteObserver1();
Observer obs2=new ConcreteObserver2();
subject.add(obs1);
subject.add(obs2);
subject.notifyObserver();
}
}
//抽象目標
abstract class Subject
{
protected List<Observer> observers=new ArrayList<Observer>();
//增加觀察者方法
public void add(Observer observer)
{
observers.add(observer);
}
//刪除觀察者方法
public void remove(Observer observer)
{
observers.remove(observer);
}
public abstract void notifyObserver(); //通知觀察者方法
}
//具體目標
class ConcreteSubject extends Subject
{
public void notifyObserver()
{
System.out.println("具體目標發生改變...");
System.out.println("--------------");
for(Object obs:observers)
{
((Observer)obs).response();
}
}
}
//抽象觀察者
interface Observer
{
void response(); //反應
}
//具體觀察者1
class ConcreteObserver1 implements Observer
{
public void response()
{
System.out.println("具體觀察者1作出反應!");
}
}
//具體觀察者1
class ConcreteObserver2 implements Observer
{
public void response()
{
System.out.println("具體觀察者2作出反應!");
}
}
4)適用場景特點
通過前面的分析與應用實例可知觀察者模式適合以下幾種情形。
對象間存在一對多關係,一個對象的狀態發生改變會影響其他對象。
當一個抽象模型有兩個方面,其中一個方面依賴於另一方面時,可將這二者封裝在獨立的對象中以使它們可以各自獨立地改變和複用。
5)擴展
在 Java 中,通過 java.util.Observable 類和 java.util.Observer 接口定義了觀察者模式,只要實現它們的子類就可以編寫觀察者模式實例。
1. Observable類
Observable 類是抽象目標類,它有一個 Vector 向量,用於保存所有要通知的觀察者對象,下面來介紹它最重要的 3 個方法。
void addObserver(Observer o) 方法:用於將新的觀察者對象添加到向量中。
void notifyObservers(Object arg) 方法:調用向量中的所有觀察者對象的 update。方法,通知它們數據發生改變。通常越晚加入向量的觀察者越先得到通知。
void setChange() 方法:用來設置一個 boolean 類型的內部標誌位,註明目標對象發生了變化。當它爲真時,notifyObservers() 纔會通知觀察者。
2. Observer 接口
Observer 接口是抽象觀察者,它監視目標對象的變化,當目標對象發生變化時,觀察者得到通知,並調用 void update(Observable o,Object arg) 方法,進行相應的工作。
【例】利用 Observable 類和 Observer 接口實現原油期貨的觀察者模式實例。
分析:當原油價格上漲時,空方傷心,多方局興;當油價下跌時,空方局興,多方傷心。本實例中的抽象目標(Observable)類在 Java 中已經定義,可以直接定義其子類,即原油期貨(OilFutures)類,它是具體目標類,該類中定義一個 SetPriCe(float price) 方法,當原油數據發生變化時調用其父類的 notifyObservers(Object arg) 方法來通知所有觀察者;另外,本實例中的抽象觀察者接口(Observer)在 Java 中已經定義,只要定義其子類,即具體觀察者類(包括多方類 Bull 和空方類 Bear),並實現 update(Observable o,Object arg) 方法即可
package observer;
import java.util.Observer;
import java.util.Observable;
public class CrudeOilFutures
{
public static void main(String[] args)
{
OilFutures oil=new OilFutures();
Observer bull=new Bull(); //多方
Observer bear=new Bear(); //空方
oil.addObserver(bull);
oil.addObserver(bear);
oil.setPrice(10);
oil.setPrice(-8);
}
}
//具體目標類:原油期貨
class OilFutures extends Observable
{
private float price;
public float getPrice()
{
return this.price;
}
public void setPrice(float price)
{
super.setChanged() ; //設置內部標誌位,註明數據發生變化
super.notifyObservers(price); //通知觀察者價格改變了
this.price=price ;
}
}
//具體觀察者類:多方
class Bull implements Observer
{
public void update(Observable o,Object arg)
{
Float price=((Float)arg).floatValue();
if(price>0)
{
System.out.println("油價上漲"+price+"元,多方高興了!");
}
else
{
System.out.println("油價下跌"+(-price)+"元,多方傷心了!");
}
}
}
//具體觀察者類:空方
class Bear implements Observer
{
public void update(Observable o,Object arg)
{
Float price=((Float)arg).floatValue();
if(price>0)
{
System.out.println("油價上漲"+price+"元,空方傷心了!");
}
else
{
System.out.println("油價下跌"+(-price)+"元,空方高興了!");
}
}
}
要了解更多的設計模式看這裏:更多的設計模式