一、適配器模式
1、三個角色
-
Target(目標抽象類):目標抽象類定義客戶所需接口,可以是一個抽象類或接口,也可以是具體類。
-
Adapter(適配器類):適配器可以調用另一個接口,作為一個轉換器,對Adaptee和Target進行適配,適配器類是適配器模式的核心,在對象適配器中,它通過繼承Target並關聯一個Adaptee對象使二者產生聯。
-
Adaptee(適配者類):適配者即被適配的角色,它定義了一個已經存在的接口,這個接口需要適配,適配者類一般是一個具體類,包含了客戶希望使用的業務方法,在某些情況下可能沒有適配者類的源代碼。
2、對象適配器模式

class Adapter extends Target {
private Adaptee adaptee; //維持一個對適配者對象的引用
public Adapter(Adaptee adaptee) {
this.adaptee=adaptee;
}
public void request() {
adaptee.specificRequest(); //轉發調用
}
}
3、類適配器模式
類適配器模式和對象適配器模式最大的區別在於適配器和適配者之間的關系不同,對象適配器模式中適配器和適配者之間是關聯關系,而類適配器模式中適配器和適配者是繼承關系。

class Adapter extends Adaptee implements Target {
public void request() {
specificRequest();
}
}
由於Java、C#等語言不支持多重類繼承,因此類適配器的使用受到很多限制,例如如果目標抽象類Target不是接口,而是一個類,就無法使用類適配器;此外,如果適配者Adapter為最終(Final)類,也無法使用類適配器。在Java等面向對象編程語言中,大部分情況下我們使用的是對象適配器,類適配器較少使用。
4、雙向適配器

class Adapter implements Target,Adaptee {
//同時維持對抽象目標類和適配者的引用
private Target target;
private Adaptee adaptee;
public Adapter(Target target) {
this.target = target;
}
public Adapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
public void request() {
adaptee.specificRequest();
}
public void specificRequest() {
target.request();
}
}
在實際開發中,我們很少使用雙向適配器。
5、缺省適配器
當不需要實現一個接口所提供的所有方法時,可先設計一個抽象類實現該接口,並為接口中每個方法提供一個默認實現(空方法),那么該抽象類的子類可以選擇性地覆蓋父類的某些方法來實現需求,它適用於不想使用一個接口中的所有方法的情況,又稱為單接口適配器模式。

在缺省適配器模式中,包含如下三個角色:
-
ServiceInterface(適配者接口):它是一個接口,通常在該接口中聲明了大量的方法。
-
AbstractServiceClass(缺省適配器類):它是缺省適配器模式的核心類,使用空方法的形式實現了在ServiceInterface接口中聲明的方法。通常將它定義為抽象類,因為對它進行實例化沒有任何意義。
-
ConcreteServiceClass(具體業務類):它是缺省適配器類的子類,在沒有引入適配器之前,它需要實現適配者接口,因此需要實現在適配者接口中定義的所有方法,而對於一些無須使用的方法也不得不提供空實現。在有了缺省適配器之后,可以直接繼承該適配器類,根據需要有選擇性地覆蓋在適配器類中定義的方法。
6、適配器模式總結
適配器
(1) 將目標類和適配者類解耦,通過引入一個適配器類來重用現有的適配者類,無須修改原有結構。
(2) 增加了類的透明性和復用性,將具體的業務實現過程封裝在適配者類中,對於客戶端類而言是透明的,而且提高了適配者的復用性,同一個適配者類可以在多個不同的系統中復用。
(3) 靈活性和擴展性都非常好,通過使用配置文件,可以很方便地更換適配器,也可以在不修改原有代碼的基礎上增加新的適配器類,完全符合“開閉原則”。
類適配器模式
優點
由於適配器類是適配者類的子類,因此可以在適配器類中置換一些適配者的方法,使得適配器的靈活性更強。
缺點
(1) 對於Java等不支持多重類繼承的語言,一次最多只能適配一個適配者類,不能同時適配多個適配者;
(2) 適配者類不能為最終類,如在Java中不能為final類;
(3) 在Java等語言中,類適配器模式中的目標抽象類只能為接口,不能為類,其使用有一定的局限性。
對象適配器模式
優點
(1) 一個對象適配器可以把多個不同的適配者適配到同一個目標;
(2) 可以適配一個適配者的子類,由於適配器和適配者之間是關聯關系,根據“里氏代換原則”,適配者的子類也可通過該適配器進行適配。
缺點
與類適配器模式相比,要在適配器中置換適配者類的某些方法比較麻煩。如果一定要置換掉適配者類的一個或多個方法,可以先做一個適配者類的子類,將適配者類的方法置換掉,然后再把適配者類的子類當做真正的適配者進行適配,實現過程較為復雜。
適用場景
(1) 系統需要使用一些現有的類,而這些類的接口(如方法名)不符合系統的需要,甚至沒有這些類的源代碼。
(2) 想創建一個可以重復使用的類,用於與一些彼此之間沒有太大關聯的一些類,包括一些可能在將來引進的類一起工作。
二、橋接模式
1、概念
橋接模式是一種很實用的結構型設計模式,如果軟件系統中某個類存在兩個獨立變化的維度,通過該模式可以將這兩個維度分離出來,使兩者可以獨立擴展,讓系統更加符合“單一職責原則”。與多層繼承方案不同,它將兩個獨立變化的維度設計為兩個獨立的繼承等級結構,並且在抽象層建立一個抽象關聯,該關聯關系類似一條連接兩個獨立繼承結構的橋,故名橋接模式。
2、四個角色
-
Abstraction(抽象類):
用於定義抽象類的接口,它一般是抽象類而不是接口,其中定義了一個Implementor(實現類接口)類型的對象並可以維護該對象,它與Implementor之間具有關聯關系,它既可以包含抽象業務方法,也可以包含具體業務方法。 -
RefinedAbstraction(擴充抽象類):擴充由Abstraction定義的接口,通常情況下它不再是抽象類而是具體類,它實現了在Abstraction中聲明的抽象業務方法,在RefinedAbstraction中可以調用在Implementor中定義的業務方法。
-
Implementor(實現類接口):定義實現類的接口,這個接口不一定要與Abstraction的接口完全一致,事實上這兩個接口可以完全不同,一般而言,Implementor接口僅提供基本操作,而Abstraction定義的接口可能會做更多更復雜的操作。Implementor接口對這些基本操作進行了聲明,而具體實現交給其子類。通過關聯關系,在Abstraction中不僅擁有自己的方法,還可以調用到Implementor中定義的方法,使用關聯關系來替代繼承關系。
-
ConcreteImplementor(具體實現類):具體實現Implementor接口,在不同的ConcreteImplementor中提供基本操作的不同實現,在程序運行時,ConcreteImplementor對象將替換其父類對象,提供給抽象類具體的業務操作方法。
在使用橋接模式時,我們首先應該識別出一個類所具有的兩個獨立變化的維度,將它們設計為兩個獨立的繼承等級結構,為兩個維度都提供抽象層,並建立抽象耦合。通常情況下,我們將具有兩個獨立變化維度的類的一些普通業務方法和與之關系最密切的維度設計為“抽象類”層次結構(抽象部分),而將另一個維度設計為“實現類”層次結構(實現部分)。
3、結構圖

4、代碼實現
abstract class Abstraction {
protected Implementor impl; //定義實現類接口對象
public void setImpl(Implementor impl) {
this.impl=impl;
}
public abstract void operation(); //聲明抽象業務方法
}
class RefinedAbstraction extends Abstraction {
public void operation() {
//業務代碼
impl.operationImpl(); //調用實現類的方法
//業務代碼
}
}
5、舉例
傳統方法:繼承
缺點:每增加一個品牌,就要增加相同的三個類

橋接模式:每增加一個品牌,只需增加一個類即可

思考:用組合代替了繼承
三、組合模式(樹形結構)
1、概念
組合模式(Composite Pattern):組合多個對象形成樹形結構以表示具有“整體—部分”關系的層次結構。組合模式對單個對象(即葉子對象)和組合對象(即容器對象)的使用具有一致性,組合模式又可以稱為“整體—部分”(Part-Whole)模式,它是一種對象結構型模式。
2、三個角色
-
Component(抽象構件):
它可以是接口或抽象類,為葉子構件和容器構件對象聲明接口,在該角色中可以包含所有子類共有行為的聲明和實現。在抽象構件中定義了訪問及管理它的子構件的方法,如增加子構件、刪除子構件、獲取子構件等。 -
Leaf(葉子構件):
在組合結構中表示葉子節點對象,葉子節點沒有子節點,它實現了在抽象構件中定義的行為。對於那些訪問及管理子構件的方法,可以通過異常等方式進行處理。 -
Composite(容器構件):
它在組合結構中表示容器節點對象,容器節點包含子節點,其子節點可以是葉子節點,也可以是容器節點,它提供一個集合用於存儲子節點,實現了在抽象構件中定義的行為,包括那些訪問及管理子構件的方法,在其業務方法中可以遞歸調用其子節點的業務方法。
組合模式的關鍵是定義了一個抽象構件類,它既可以代表葉子,又可以代表容器,而客戶端針對該抽象構件類進行編程,無須知道它到底表示的是葉子還是容器,可以對其進行統一處理。同時容器對象與抽象構件類之間還建立一個聚合關聯關系,在容器對象中既可以包含葉子,也可以包含容器,以此實現遞歸組合,形成一個樹形結構。
3、結構圖
方案一:安全組合
缺點是不夠透明,因為葉子構件和容器構件具有不同的方法,且容器構件中那些用於管理成員對象的方法沒有在抽象構件類中定義,因此客戶端不能完全針對抽象編程,必須有區別地對待葉子構件和容器構件。

方案二:透明組合
明組合模式的缺點是不夠安全,因為葉子對象和容器對象在本質上是有區別的。葉子對象不可能有下一個層次的對象,即不可能包含成員對象,因此為其提供add()、remove()以及getChild()等方法是沒有意義的,這在編譯階段不會出錯,但在運行階段如果調用這些方法可能會出錯(如果沒有提供相應的錯誤處理代碼)。

4、代碼實現
抽象類
abstract class Component {
public abstract void add(Component c); //增加成員
public abstract void remove(Component c); //刪除成員
public abstract Component getChild(int i); //獲取成員
public abstract void operation(); //業務方法
}
葉子
class Leaf extends Component {
public void add(Component c) {
//異常處理或錯誤提示
}
public void remove(Component c) {
//異常處理或錯誤提示
}
public Component getChild(int i) {
//異常處理或錯誤提示
return null;
}
public void operation() {
//葉子構件具體業務方法的實現
}
}
容器
class Composite extends Component {
private ArrayList<Component> list = new ArrayList<Component>();
public void add(Component c) {
list.add(c);
}
public void remove(Component c) {
list.remove(c);
}
public Component getChild(int i) {
return (Component)list.get(i);
}
public void operation() {
//容器構件具體業務方法的實現
//遞歸調用成員構件的業務方法
for(Object obj:list) {
((Component)obj).operation();
}
}
}
四、裝飾模式
1、概念
裝飾模式是一種用於替代繼承的技術,它通過一種無須定義子類的方式來給對象動態增加職責,使用對象之間的關聯關系取代類之間的繼承關系。在裝飾模式中引入了裝飾類,在裝飾類中既可以調用待裝飾的原有類的方法,還可以增加新的方法,以擴充原有類的功能。
2、四個角色
-
Component(抽象構件):
它是具體構件和抽象裝飾類的共同父類,聲明了在具體構件中實現的業務方法,它的引入可以使客戶端以一致的方式處理未被裝飾的對象以及裝飾之后的對象,實現客戶端的透明操作。 -
ConcreteComponent(具體構件):
它是抽象構件類的子類,用於定義具體的構件對象,實現了在抽象構件中聲明的方法,裝飾器可以給它增加額外的職責(方法)。 -
Decorator(抽象裝飾類):
它也是抽象構件類的子類,用於給具體構件增加職責,但是具體職責在其子類中實現。它維護一個指向抽象構件對象的引用,通過該引用可以調用裝飾之前構件對象的方法,並通過其子類擴展該方法,以達到裝飾的目的。 -
ConcreteDecorator(具體裝飾類):
它是抽象裝飾類的子類,負責向構件添加新的職責。每一個具體裝飾類都定義了一些新的行為,它可以調用在抽象裝飾類中定義的方法,並可以增加新的方法用以擴充對象的行為。
3、結構圖

4、代碼實現
class Decorator implements Component
{
private Component component; //維持一個對抽象構件對象的引用
public Decorator(Component component) //注入一個抽象構件類型的對象
{
this.component=component;
}
public void operation()
{
component.operation(); //調用原有業務方法
}
}
需要注意的是在Decorator中並未真正實現operation()方法,而只是調用原有component對象的operation()方法,它沒有真正實施裝飾,而是提供一個統一的接口,將具體裝飾過程交給子類完成。
class ConcreteDecorator extends Decorator
{
public ConcreteDecorator(Component component)
{
super(component);
}
public void operation()
{
super.operation(); //調用原有業務方法
addedBehavior(); //調用新增業務方法
}
//新增業務方法
public void addedBehavior()
{
……
}
}
5、優點示意圖
裝飾比繼承 減少了大量子類,且修改類別時更靈活
前:

后:

6、透明與半透明裝飾模式
1)透明裝飾模式
在透明裝飾模式中,要求客戶端完全針對抽象編程,裝飾模式的透明性要求客戶端程序不應該將對象聲明為具體構件類型或具體裝飾類型,而應該全部聲明為抽象構件類型。對於客戶端而言,具體構件對象和具體裝飾對象沒有任何區別。
Component c, d; //使用抽象構件類型定義對象
c = new ConcreteComponent();
d = new ConcreteDecorator (c);
透明裝飾模式可以讓客戶端透明地使用裝飾之前的對象和裝飾之后的對象,無須關心它們的區別,此外,還可以對一個已裝飾過的對象進行多次裝飾,得到更為復雜、功能更為強大的對象。在實現透明裝飾模式時,要求具體裝飾類的operation()方法覆蓋抽象裝飾類的operation()方法,除了調用原有對象的operation()外還需要調用新增的addedBehavior()方法來增加新行為,
2)半透明裝飾模式
透明裝飾模式的設計難度較大,而且有時我們需要單獨調用新增的業務方法。為了能夠調用到新增方法,我們不得不用具體裝飾類型來定義裝飾之后的對象,而具體構件類型還是可以使用抽象構件類型來定義,這種裝飾模式即為半透明裝飾模式,也就是說,對於客戶端而言,具體構件類型無須關心,是透明的;但是具體裝飾類型必須指定,這是不透明的。
Component c; //使用抽象構件類型定義對象
c = new ConcreteComponent();
ConcreteDecorator d;
d = new ConcreteDecorator (c);
半透明裝飾模式可以給系統帶來更多的靈活性,設計相對簡單,使用起來也非常方便;但是其最大的缺點在於不能實現對同一個對象的多次裝飾,而且客戶端需要有區別地對待裝飾之前的對象和裝飾之后的對象。
7、裝飾模式總結
裝飾模式降低了系統的耦合度,可以動態增加或刪除對象的職責,並使得需要裝飾的具體構件類和具體裝飾類可以獨立變化,以便增加新的具體構件類和具體裝飾類。在軟件開發中,裝飾模式應用較為廣泛,例如在JavaIO中的輸入流和輸出流的設計、javax.swing包中一些圖形界面構件功能的增強等地方都運用了裝飾模式。
-
主要優點
(1) 對於擴展一個對象的功能,裝飾模式比繼承更加靈活性,不會導致類的個數急劇增加。
(2) 可以通過一種動態的方式來擴展一個對象的功能,通過配置文件可以在運行時選擇不同的具體裝飾類,從而實現不同的行為。
(3) 可以對一個對象進行多次裝飾,通過使用不同的具體裝飾類以及這些裝飾類的排列組合,可以創造出很多不同行為的組合,得到功能更為強大的對象。
(4) 具體構件類與具體裝飾類可以獨立變化,用戶可以根據需要增加新的具體構件類和具體裝飾類,原有類庫代碼無須改變,符合“開閉原則”。 -
主要缺點
(1) 使用裝飾模式進行系統設計時將產生很多小對象,這些對象的區別在於它們之間相互連接的方式有所不同,而不是它們的類或者屬性值有所不同,大量小對象的產生勢必會占用更多的系統資源,在一定程序上影響程序的性能。
(2) 裝飾模式提供了一種比繼承更加靈活機動的解決方案,但同時也意味着比繼承更加易於出錯,排錯也很困難,對於多次裝飾的對象,調試時尋找錯誤可能需要逐級排查,較為繁瑣。
五、外觀模式
1、概念
外觀模式:為子系統中的一組接口提供一個統一的入口。外觀類將客戶類與子系統的內部復雜性分隔開,使得客戶類只需要與外觀角色打交道,而不需要與子系統內部的很多對象打交道。
2、兩個角色
-
Facade(外觀角色):
在客戶端可以調用它的方法,在外觀角色中可以知道相關的(一個或者多個)子系統的功能和責任;在正常情況下,它將所有從客戶端發來的請求委派到相應的子系統去,傳遞給相應的子系統對象處理。 -
SubSystem(子系統角色):
在軟件系統中可以有一個或者多個子系統角色,每一個子系統可以不是一個單獨的類,而是一個類的集合,它實現子系統的功能;每一個子系統都可以被客戶端直接調用,或者被外觀角色調用,它處理由外觀類傳過來的請求;子系統並不知道外觀的存在,對於子系統而言,外觀角色僅僅是另外一個客戶端而已。
3、結構圖

4、代碼實現
由於在外觀類中維持了對子系統對象的引用,客戶端可以通過外觀類來間接調用子系統對象的業務方法,而無須與子系統對象直接交互。引入外觀類后,客戶端代碼變得非常簡單。
class Facade
{
private SubSystemA obj1 = new SubSystemA();
private SubSystemB obj2 = new SubSystemB();
private SubSystemC obj3 = new SubSystemC();
public void method()
{
obj1.methodA();
obj2.methodB();
obj3.methodC();
}
}
5、改進:抽象外觀類
在標准的外觀模式結構圖中,如果需要增加、刪除或更換與外觀類交互的子系統類,必須修改外觀類或客戶端的源代碼,這將違背開閉原則。
因此可以通過引入抽象外觀類來對系統進行改進,客戶端針對抽象外觀類進行編程,對於新的業務需求,對應增加一個新的具體外觀類,由新的具體外觀類來關聯新的子系統對象,
六、享元模式
1、概念
享元模式通過共享技術實現相同或相似對象的重用,在邏輯上每一個出現的字符都有一個對象與之對應,然而在物理上它們卻共享同一個享元對象。
享元對象能做到共享的關鍵是區分了內部狀態(Intrinsic State)和外部狀態(Extrinsic State)。
(1) 內部狀態是存儲在享元對象內部並且不會隨環境改變而改變的狀態,內部狀態可以共享。如字符的內容,不會隨外部環境的變化而變化,無論在任何環境下字符“a”始終是“a”,都不會變成“b”。
(2) 外部狀態是隨環境改變而改變的、不可以共享的狀態。享元對象的外部狀態通常由客戶端保存,並在享元對象被創建之后,需要使用的時候再傳入到享元對象內部。一個外部狀態與另一個外部狀態之間是相互獨立的。如字符的顏色,可以在不同的地方有不同的顏色,例如有的“a”是紅色的,有的“a”是綠色的,字符的大小也是如此,有的“a”是五號字,有的“a”是四號字。而且字符的顏色和大小是兩個獨立的外部狀態,它們可以獨立變化,相互之間沒有影響,客戶端可以在使用時將外部狀態注入享元對象中。
2、四個角色
-
Flyweight(抽象享元類):
通常是一個接口或抽象類,在抽象享元類中聲明了具體享元類公共的方法,這些方法可以向外界提供享元對象的內部數據(內部狀態),同時也可以通過這些方法來設置外部數據(外部狀態)。 -
ConcreteFlyweight(具體享元類):
它實現了抽象享元類,其實例稱為享元對象;在具體享元類中為內部狀態提供了存儲空間。通常我們可以結合單例模式來設計具體享元類,為每一個具體享元類提供唯一的享元對象。 -
UnsharedConcreteFlyweight(非共享具體享元類):
並不是所有的抽象享元類的子類都需要被共享,不能被共享的子類可設計為非共享具體享元類;當需要一個非共享具體享元類的對象時可以直接通過實例化創建。 -
FlyweightFactory(享元工廠類):
享元工廠類用於創建並管理享元對象,它針對抽象享元類編程,將各種類型的具體享元對象存儲在一個享元池中,享元池一般設計為一個存儲“鍵值對”的集合(也可以是其他類型的集合),可以結合工廠模式進行設計;當用戶請求一個具體享元對象時,享元工廠提供一個存儲在享元池中已創建的實例或者創建一個新的實例(如果不存在的話),返回新創建的實例並將其存儲在享元池中。
3、結構圖

4、代碼實現
class FlyweightFactory {
//定義一個HashMap用於存儲享元對象,實現享元池
private HashMap flyweights = newHashMap();
public Flyweight getFlyweight(String key){
//如果對象存在,則直接從享元池獲取
if(flyweights.containsKey(key)){
return(Flyweight)flyweights.get(key);
}
//如果對象不存在,先創建一個新的對象添加到享元池中,然后返回
else {
Flyweight fw = newConcreteFlyweight();
flyweights.put(key,fw);
return fw;
}
}
}
class Flyweight {
//內部狀態intrinsicState作為成員變量,同一個享元對象其內部狀態是一致的
private String intrinsicState;
public Flyweight(String intrinsicState) {
this.intrinsicState=intrinsicState;
}
//外部狀態extrinsicState在使用時由外部設置,不保存在享元對象中,即使是同一個對象,在每一次調用時也可以傳入不同的外部狀態
public void operation(String extrinsicState) {
......
}
}
七、代理模式
1、概念
給某一個對象提供一個代理或占位符,並由代理對象來控制對原對象的訪問。
2、三個角色
-
Subject(抽象主題角色):
它聲明了真實主題和代理主題的共同接口,這樣一來在任何使用真實主題的地方都可以使用代理主題,客戶端通常需要針對抽象主題角色進行編程。 -
Proxy(代理主題角色):
它包含了對真實主題的引用,從而可以在任何時候操作真實主題對象;在代理主題角色中提供一個與真實主題角色相同的接口,以便在任何時候都可以替代真實主題;代理主題角色還可以控制對真實主題的使用,負責在需要的時候創建和刪除真實主題對象,並對真實主題對象的使用加以約束。通常,在代理主題角色中,客戶端在調用所引用的真實主題操作之前或之后還需要執行其他操作,而不僅僅是單純調用真實主題對象中的操作。 -
RealSubject(真實主題角色):
它定義了代理角色所代表的真實對象,在真實主題角色中實現了真實的業務操作,客戶端可以通過代理主題角色間接調用真實主題角色中定義的操作。
3、結構圖

4、代碼實現
抽象主題類聲明了真實主題類和代理類的公共方法,它可以是接口、抽象類或具體類,客戶端針對抽象主題類編程,一致性地對待真實主題和代理主題,典型的抽象主題類代碼如下:
abstract class Subject
{
public abstract void Request();
}
真實主題類繼承了抽象主題類,提供了業務方法的具體實現,其典型代碼如下:
class RealSubject extends Subject
{
public override void Request()
{
//業務方法具體實現代碼
}
}
代理類也是抽象主題類的子類,它維持一個對真實主題對象的引用,調用在真實主題中實現的業務方法,在調用時可以在原有業務方法的基礎上附加一些新的方法來對功能進行擴充或約束,最簡單的代理類實現代碼如下:
class Proxy extends Subject
{
private RealSubject realSubject = new RealSubject(); //維持一個對真實主題對象的引用
public void PreRequest()
{
…...
}
public override void Request()
{
PreRequest();
realSubject.Request(); //調用真實主題對象的方法
PostRequest();
}
public void PostRequest()
{
……
}
}