常用設(shè)計(jì)模式與設(shè)計(jì)原則

I. 引言

設(shè)計(jì)模式(Design Pattern)是一套被反復(fù)使用、多數(shù)人知曉的、經(jīng)過(guò)分類(lèi)的、代碼設(shè)計(jì)經(jīng)驗(yàn)的總結(jié)。

使用設(shè)計(jì)模式的目的:為了代碼可重用性、讓代碼更容易被他人理解、保證代碼可靠性。 設(shè)計(jì)模式使代碼編寫(xiě)真正工程化;設(shè)計(jì)模式是軟件工程的基礎(chǔ),如同大廈的結(jié)構(gòu)一樣。

創(chuàng)建型模式:對(duì)類(lèi)的實(shí)例化過(guò)程的抽象。一些系統(tǒng)在創(chuàng)建對(duì)象時(shí),需要?jiǎng)討B(tài)地決定怎樣創(chuàng)建對(duì)象,創(chuàng)建哪些對(duì)象,以及如何組合和表示這些對(duì)象。創(chuàng)建模式描述了怎樣構(gòu)造和封裝這些動(dòng)態(tài)的決定。包含類(lèi)的創(chuàng)建模式和對(duì)象的創(chuàng)建模式。

結(jié)構(gòu)型模式:描述如何將類(lèi)或?qū)ο蠼Y(jié)合在一起形成更大的結(jié)構(gòu)。分為類(lèi)的結(jié)構(gòu)模式和對(duì)象的結(jié)構(gòu)模式。類(lèi)的結(jié)構(gòu)模式使用繼承把類(lèi),接口等組合在一起,以形成更大的結(jié)構(gòu)。類(lèi)的結(jié)構(gòu)模式是靜態(tài)的。對(duì)象的結(jié)構(gòu)模式描述怎樣把各種不同類(lèi)型的對(duì)象組合在一起,以實(shí)現(xiàn)新的功能的方法。對(duì)象的結(jié)構(gòu)模式是動(dòng)態(tài)的。

行為型模式:對(duì)在不同的對(duì)象之間劃分責(zé)任和算法的抽象化。不僅僅是關(guān)于類(lèi)和對(duì)象的,并是關(guān)于他們之間的相互作用。類(lèi)的行為模式使用繼承關(guān)系在幾個(gè)類(lèi)之間分配行為。對(duì)象的行為模式則使用對(duì)象的聚合來(lái)分配行為。

設(shè)計(jì)模式分為三種類(lèi)型,共23種。

  • 創(chuàng)建型模式:?jiǎn)卫J?、抽象工廠(chǎng)模式、建造者模式、工廠(chǎng)模式、原型模式。

  • 結(jié)構(gòu)型模式:適配器模式、橋接模式、裝飾模式、組合模式、外觀(guān)模式、享元模式、代理模式。

  • 行為型模式:模版方法模式、命令模式、迭代器模式、觀(guān)察者模式、中介者模式、備忘錄模式、解釋器模式、狀態(tài)模式、策略模式、責(zé)任鏈模式、訪(fǎng)問(wèn)者模式。

設(shè)計(jì)模式分類(lèi)

下面將對(duì)一些常用的設(shè)計(jì)模式和軟件設(shè)計(jì)的六大原則進(jìn)行介紹。

一. 創(chuàng)建型模式

1. Factory method 工廠(chǎng)方法(工廠(chǎng)模式)

作用

一個(gè)類(lèi)需要一個(gè)產(chǎn)品來(lái)完成某項(xiàng)工作,但它不能確定,也不關(guān)心具體拿到什么產(chǎn)品,因此它定義一個(gè)工廠(chǎng)方法,將具體產(chǎn)品的生產(chǎn)延遲到子類(lèi)決定。

實(shí)現(xiàn)

工廠(chǎng)模式
  1. 父類(lèi)可以選擇為工廠(chǎng)方法提供一個(gè)默認(rèn)的實(shí)現(xiàn);
  2. 工廠(chǎng)方法通常在模板方法(Template method)中被調(diào)用,上圖中AnOperation()就是一個(gè)模板方法。

2. Abstract factory 抽象工廠(chǎng)

作用

系統(tǒng)有一組相互關(guān)聯(lián)的產(chǎn)品接口,及幾套不同的實(shí)現(xiàn)。客戶(hù)只依賴(lài)產(chǎn)品接口,并需要能靈活地在幾套實(shí)現(xiàn)中切換。

因此提供一個(gè)抽象工廠(chǎng)生產(chǎn)抽象的產(chǎn)品,每個(gè)產(chǎn)品在其中都對(duì)應(yīng)一個(gè)工廠(chǎng)方法,產(chǎn)品族的每一套實(shí)現(xiàn)都提供一個(gè)具體工廠(chǎng)??蛻?hù)通過(guò)抽象工廠(chǎng)獲取產(chǎn)品,當(dāng)需要切換到產(chǎn)品的其他實(shí)現(xiàn)時(shí)只需要更換工廠(chǎng)的實(shí)現(xiàn)類(lèi)。

實(shí)現(xiàn)

抽象工廠(chǎng)

應(yīng)用

根據(jù)底層數(shù)據(jù)源的不同,DAO的實(shí)現(xiàn)通常有幾套,當(dāng)切換數(shù)據(jù)源時(shí),系統(tǒng)使用的DAO的實(shí)現(xiàn)也應(yīng)當(dāng)能快速切換。這是使用抽象工廠(chǎng)的一個(gè)典型場(chǎng)景。

3. Singleton 單例

作用

保證一個(gè)類(lèi)只有一個(gè)對(duì)象

實(shí)現(xiàn)

  1. private構(gòu)造器
  2. private static 類(lèi)變量 singleton
  3. public static 類(lèi)方法 getInstance() 返回singleton。

實(shí)例化時(shí)機(jī):

  1. eager
  2. lazy

lazy init 多線(xiàn)程問(wèn)題的解決辦法:Double Check

private volatile static A singleton = null;

public static A getInstance(){
    if(singleton == null){
        sychronized(A.class){
            if(singleton == null) singleton = new A();
        }
    }
    return singleton;
}

private A(){}
  1. 為什么要第二次的null判斷?
    在第一次判null / 獲取鎖之間可能有其他線(xiàn)程實(shí)例化了。
  2. 為什么要volatile?
    在上面提到的情況下,如果沒(méi)有volatile保證的可見(jiàn)性,在第二次null判斷時(shí)當(dāng)前線(xiàn)程可能看不到別的線(xiàn)程創(chuàng)建的對(duì)象,從而通過(guò)并再創(chuàng)建一次。

static 內(nèi)部類(lèi)利用 “類(lèi)的加載/static塊是線(xiàn)程安全的” 實(shí)現(xiàn)線(xiàn)程安全的lazy init:

public class A{
    private static class Holder{
        private static A singleton = new A();
    }

    public static A getInstance(){
        return Holder.singleton;
    }

    private A(){}
}

4. Builder 建造者模式

作用

你有一個(gè)產(chǎn)品,該產(chǎn)品由若干part裝配而成,裝配的邏輯是固定的,但各個(gè)part的構(gòu)造是可切換選擇的,Builder模式將 固定的裝配邏輯易變的part構(gòu)造邏輯 分離開(kāi),可以方便地在不同的part實(shí)現(xiàn)邏輯之間切換。

實(shí)現(xiàn)

建造者模式
  1. Director#construct() 負(fù)責(zé)固定的裝配邏輯;
  2. 一個(gè)Builder實(shí)例負(fù)責(zé)一個(gè)產(chǎn)品內(nèi)部所有part的構(gòu)造(buildPart()方法族),并向外部暴露方法,在part都裝配完畢后獲取該產(chǎn)品。

交互

建造者模式交互

5. Prototype 原型模式

作用

用原型實(shí)例指定創(chuàng)建對(duì)象的種類(lèi),并且通過(guò)拷貝這些原型創(chuàng)建新的對(duì)象。

適用性

  • 當(dāng)要實(shí)例化的類(lèi)是在運(yùn)行時(shí)刻指定時(shí),例如,通過(guò)動(dòng)態(tài)裝載;

  • 為了避免創(chuàng)建一個(gè)與產(chǎn)品類(lèi)層次平行的工廠(chǎng)類(lèi)層次時(shí);

  • 當(dāng)一個(gè)類(lèi)的實(shí)例只能有幾個(gè)不同狀態(tài)組合中的一種時(shí)。建立相應(yīng)數(shù)目的原型并克隆它們可能比每次用合適的狀態(tài)手工實(shí)例化該類(lèi)更方便一些。

實(shí)現(xiàn)

原型模式

二. 結(jié)構(gòu)型模式

1. Adapter 適配器

作用

在兩個(gè)不兼容的接口之間加一個(gè)中間層,用組合的方式將一個(gè)現(xiàn)有對(duì)象匹配到需要的接口。

Convert the interface of a class into another interface the client expects

實(shí)現(xiàn)

適配器模式

2. Proxy 代理

作用

Provide a surrogate or place holder for another object to control access to it

你有一個(gè)真正干活的對(duì)象RealSubject,但需要向client控制對(duì)他的訪(fǎng)問(wèn),比如權(quán)限的控制 / Lazy load / 結(jié)果的緩存等等,因此在client和RealSubject之間增加一個(gè)中間層Proxy代替RealSubject,Proxy包裹RealSubject,將具體功能實(shí)現(xiàn)委托給它,并在RealSubject執(zhí)行真正的功能前后插入自己的邏輯;此外,Proxy向client隱藏了RealSubject的存在。

實(shí)現(xiàn)

代理模式

Decorator模式的區(qū)別

Proxy與Decorator有著相似的結(jié)構(gòu),** 他們都在client和真實(shí)對(duì)象之間增加一個(gè)與真實(shí)對(duì)象實(shí)現(xiàn)了相同接口的中間層,這個(gè)中間層保留了對(duì)真實(shí)對(duì)象的引用并向他們發(fā)送請(qǐng)求**。然而他們的設(shè)計(jì)目的是不同的:

Decorator側(cè)重動(dòng)態(tài)為實(shí)體增加功能,因此在該模式中:

  1. 實(shí)體只實(shí)現(xiàn)了部分功能,Decorator實(shí)現(xiàn)了其他的增強(qiáng)功能;
  2. 支持遞歸組合(增加多重功能);
  3. Decorator不知道自己裝飾的是哪個(gè)具體對(duì)象,client必須自己手動(dòng)將實(shí)體和Decorator關(guān)聯(lián)起來(lái)。

Proxy的目的則是當(dāng)訪(fǎng)問(wèn)一個(gè)特定實(shí)體不方便或不符合要求時(shí),為這個(gè)實(shí)體提供一個(gè)替代者,因此:

  1. 實(shí)體實(shí)現(xiàn)了關(guān)鍵功能,Proxy提供(或拒絕)對(duì)它的訪(fǎng)問(wèn);
  2. 不支持遞歸組合;
  3. Proxy向client屏蔽RealSubject的存在,client只能拿到Proxy;
  4. Proxy確定地知道自己的代理目標(biāo)是RealSubject,因此它和RealSubject相關(guān)聯(lián)而不是Subject接口;此外,它們的關(guān)系是靜態(tài)的,無(wú)法在運(yùn)行時(shí)改變Proxy代理的目標(biāo)對(duì)象。

3. Bridge 橋接模式

作用

你有一個(gè)產(chǎn)品,它在兩個(gè)維度上都是可變化的,如果用繼承,則需要n*m個(gè)子類(lèi)。Bridge模式將兩個(gè)維度的繼承體系獨(dú)立出來(lái),并在二者之間用組合進(jìn)行裝配,避免類(lèi)的泛濫。

進(jìn)一步地考慮,一個(gè)產(chǎn)品的繼承體系應(yīng)該只有一個(gè)維度,如果出現(xiàn)了其他維度上的繼承,要考慮該維度是否是行為/實(shí)現(xiàn)相關(guān)的。對(duì)于行為/實(shí)現(xiàn)方面的變化,應(yīng)當(dāng)先把行為獨(dú)立地抽象出來(lái),并與原產(chǎn)品組合(這就是策略模式的含義),而不應(yīng)該直接在原產(chǎn)品上通過(guò)繼承表達(dá)該行為的變化。

舉個(gè)例子,假如系統(tǒng)內(nèi)要發(fā)送消息,消息按迫切程度分為普通/加急/特急,消息的發(fā)送形式也可以多樣,比如站內(nèi)信/短信/email,每種消息都要求可以用任意方式發(fā)送:

發(fā)送消息

如果簡(jiǎn)單地用繼承,則需要33 = 9個(gè)類(lèi)。但實(shí)際上,消息的發(fā)送* 這個(gè)維度屬于行為,不要用繼承來(lái)表達(dá)行為的變化,這樣會(huì)污染原本的抽象層次,應(yīng)當(dāng)用策略模式消息發(fā)送 這個(gè)行為分離。

采用Bridge模式:


Bridge模式

實(shí)現(xiàn)

橋接模式

產(chǎn)品的抽象 + 行為的分離(策略模式

總結(jié)

Bridge模式在我看來(lái)是對(duì)策略模式的擴(kuò)展,它的核心有兩點(diǎn):

  1. 只在一個(gè)維度上用繼承,出現(xiàn)了多個(gè)維度則考慮分離并用組合,避免類(lèi)的泛濫和抽象維度的混雜;
  2. 策略模式隔離行為的變化,不要讓行為/實(shí)現(xiàn)的變化污染原本的繼承體系。

4. Decorator 裝飾者

作用

有一系列產(chǎn)品,你希望動(dòng)態(tài)地為他們添加額外 / 可自由組合 的功能,并且不影響產(chǎn)品本身。

Attach additional responsibilities to an object dynamically.

實(shí)現(xiàn)

裝飾者模式
  1. Decorator 裝飾器繼承產(chǎn)品抽象接口,并在內(nèi)部持有一個(gè)產(chǎn)品(可能是具體產(chǎn)品,也有可能被裝飾過(guò)了);
  2. Decorator的具體實(shí)現(xiàn),為其裝飾的產(chǎn)品提供額外的功能,類(lèi)似遞歸的調(diào)用;
  3. 可以同時(shí)反復(fù)應(yīng)用多個(gè) Decorator ,實(shí)現(xiàn)額外功能的動(dòng)態(tài)組合。

應(yīng)用

  1. java的IO流的設(shè)計(jì)是一個(gè)典型的裝飾者模式:


    IO Stream設(shè)計(jì)

ByteArrayInputStream | FileInputStream | ObjectInputStream | StringBufferInputStream是具體的輸入流產(chǎn)品,根據(jù)數(shù)據(jù)來(lái)源區(qū)分;
FilterInputStream是裝飾器;
BufferedInputStream | DataInputStream | LineNumberInputStream | PushbackInputStream是具體的裝飾器,分別為其他輸入流提供緩沖/類(lèi)型讀寫(xiě)/跟蹤行號(hào)/退回已讀數(shù)據(jù)的功能,這些裝飾器是可以組合使用的:

InputStream in = new DataInputStream(new BufferedInputStream(new FileInputStream("test.txt"))); 
  1. Decorator模式也可以用來(lái)實(shí)現(xiàn)AOP的類(lèi)似功能(雖然實(shí)際大部分都是用JDK動(dòng)態(tài)代理 / 運(yùn)行時(shí)修改字節(jié)碼),Decorator的具體實(shí)現(xiàn)就是我們想要獨(dú)立出來(lái)的切面,產(chǎn)品的具體實(shí)現(xiàn)則是我們想要保持獨(dú)立的業(yè)務(wù)邏輯。

5. Composite 組合模式

作用

實(shí)現(xiàn)樹(shù)形結(jié)構(gòu),并讓用戶(hù)可以用統(tǒng)一的接口對(duì)待葉子節(jié)點(diǎn)和非葉子節(jié)點(diǎn)。

實(shí)現(xiàn)

組合模式
  1. 操作孩子的方法應(yīng)該放在Component中嗎?畢竟Leaf是不支持這些操作的。
    出于透明性考慮,應(yīng)該放在Component中,Leaf對(duì)這些方法就提供一個(gè)空的實(shí)現(xiàn)。
  2. Component除了保存孩子,也可以記錄父親;

應(yīng)用

UI / 人員組織管理這種典型的樹(shù)形結(jié)構(gòu)中用的比較廣泛。

6. Facade 門(mén)面模式

作用

一個(gè)系統(tǒng)對(duì)外提供服務(wù),系統(tǒng)暴露的接口應(yīng)該是簡(jiǎn)單而統(tǒng)一的,客戶(hù)不應(yīng)該直接和系統(tǒng)內(nèi)復(fù)雜的子部件進(jìn)行交互,而應(yīng)只依賴(lài)于一個(gè)單一的高層接口,該組件為客戶(hù)屏蔽了內(nèi)部的復(fù)雜性,降低了客戶(hù)和系統(tǒng)的耦合。

門(mén)面模式

更像是一種設(shè)計(jì)思路,而非一個(gè)具體模式。


三. 行為模式

1. Chain of Responsibility 責(zé)任鏈

作用

客戶(hù)端發(fā)出一個(gè)請(qǐng)求,有一系列的處理器都有機(jī)會(huì)處理這個(gè)請(qǐng)求,但具體哪個(gè)是運(yùn)行時(shí)決定的,客戶(hù)端也不知道究竟誰(shuí)會(huì)來(lái)處理。

因此將所有處理器組成一個(gè)鏈條,將請(qǐng)求從鏈條中流過(guò),每個(gè)處理器查看是否應(yīng)該處理它,如果不是,則交給后面的處理器,否則處理并退出。處理器在鏈中的位置決定其優(yōu)先級(jí)。

將請(qǐng)求者和處理者解耦,可以動(dòng)態(tài)切換/組合處理者。

實(shí)現(xiàn)

責(zé)任鏈模式

擴(kuò)展

客戶(hù)端發(fā)出一個(gè)請(qǐng)求,請(qǐng)求的處理分為很多步驟,這些步驟是不確定的/可以動(dòng)態(tài)組合的,甚至需要支持在運(yùn)行時(shí)改變步驟,或者在步驟間任意跳轉(zhuǎn)。

解決方案和責(zé)任鏈類(lèi)似,將處理流程抽象為一個(gè)處理器鏈條,鏈條的組裝交給外部決定。每個(gè)處理器對(duì)請(qǐng)求完成自己負(fù)責(zé)的業(yè)務(wù)邏輯,并看情況結(jié)束/傳遞給下一個(gè)處理器/跳轉(zhuǎn)到任意處理器。

這和標(biāo)準(zhǔn)的責(zé)任鏈的結(jié)構(gòu)基本一樣,但他們的目的不一樣。標(biāo)準(zhǔn)責(zé)任鏈目的是動(dòng)態(tài) 找到請(qǐng)求的處理者 ;擴(kuò)展(某些地方稱(chēng)為“功能鏈”?)則是為了獲取 動(dòng)態(tài)拼裝和改變處理流程 的能力。

應(yīng)用

標(biāo)準(zhǔn)責(zé)任鏈

UI中的事件冒泡機(jī)制是責(zé)任鏈的一個(gè)典型應(yīng)用。HTML中,點(diǎn)擊一個(gè)DOM元素,產(chǎn)生的click事件將依次冒泡給它的父元素,每個(gè)父元素上都可以注冊(cè)對(duì)click事件的監(jiān)聽(tīng)器,監(jiān)聽(tīng)器中除了對(duì)事件處理外,也可以結(jié)束事件的繼續(xù)冒泡。

擴(kuò)展(功能鏈)
  1. Web應(yīng)用中的各種filter/攔截器;
  2. Netty中的pipeline

2. Command 命令

Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations.

你需要向一個(gè)對(duì)象提交請(qǐng)求,但對(duì)請(qǐng)求的處理是動(dòng)態(tài)的,無(wú)法寫(xiě)死。比如一個(gè)菜單項(xiàng),在不同的上下文中,點(diǎn)擊它要做的事情顯然是不一樣的。

Command模式的思路是 抽象請(qǐng)求(及處理)

命令模式
  1. Client 裝配 InvokerCommand,** 如果需要不同的處理,裝配不同的Command即可 **;
  2. ClientInvoker 發(fā)出請(qǐng)求;
  3. Invoker 將請(qǐng)求的處理委托給 Command#execute()。

很多時(shí)候Command不夠智能,自己無(wú)法處理請(qǐng)求,需要將請(qǐng)求委托給另一個(gè)Receiver進(jìn)行真正的處理,ConcreteCommand可以認(rèn)為是Receiver的適配器:

Command適配器

可以看到,Command模式的最大價(jià)值在于:隔離 請(qǐng)求的接收者請(qǐng)求的處理邏輯;
此外,將請(qǐng)求及其處理邏輯抽象為Command后可以做很多有意思的事情:

1. 可撤銷(xiāo)的操作
在Command接口中增加一個(gè)接口 undo() 實(shí)現(xiàn)單個(gè)命令的撤銷(xiāo)動(dòng)作,并用一個(gè)stack保存所有Command;當(dāng)用戶(hù)觸發(fā)撤銷(xiāo)時(shí)依次從stack pop出最近的Command,執(zhí)行其undo()方法。

2. 宏命令
宏命令實(shí)質(zhì)是個(gè)樹(shù)形結(jié)構(gòu),對(duì)Command應(yīng)用Composite 組合模式即可實(shí)現(xiàn):

宏命令實(shí)現(xiàn)

3. 排隊(duì)
4. 日志記錄和恢復(fù)

3. Memento 備忘錄

有些情況下你需要記錄一個(gè)對(duì)象(稱(chēng)為Originator)在某個(gè)時(shí)刻的狀態(tài)(snapshot),以便后續(xù)恢復(fù),我們可以用一個(gè)類(lèi)Memento表示snapshot,它包含了Originator的部分或全部狀態(tài):

備忘錄模式
  1. Originator 負(fù)責(zé)創(chuàng)建Memento,以及恢復(fù)到某個(gè)Memento;
  2. Memento 即Originator的snapshot;
  3. Caretaker 充當(dāng)協(xié)調(diào)者,它負(fù)責(zé)向Originator請(qǐng)求當(dāng)前Memento / 保存Memento / 在后續(xù)某個(gè)時(shí)刻讓Originator恢復(fù)到某個(gè)Memento。

但這里有一個(gè)問(wèn)題,為了隱藏Originator的實(shí)現(xiàn)細(xì)節(jié),Memento必須向外部隱藏內(nèi)部數(shù)據(jù),即不開(kāi)放state 的 getter/setter 給外部,但這樣一來(lái),Originator也無(wú)法創(chuàng)建Memento了。

為了解決這個(gè)問(wèn)題,在Memento模式的一般實(shí)現(xiàn)中,Memento 類(lèi)被分為兩個(gè)部分:

備忘錄
  1. 標(biāo)記接口 Memento,空的,Caretaker只能得到這個(gè)接口;
  2. Memento的真正實(shí)現(xiàn)MementoImpl,作為Originator私有內(nèi)部類(lèi) ,這樣既允許Originator訪(fǎng)問(wèn)Memento的內(nèi)部狀態(tài),又滿(mǎn)足了Memento向外部(主要是Caretaker)隱藏內(nèi)部細(xì)節(jié)的要求。

如果對(duì) Memento 的封裝性沒(méi)有嚴(yán)格的要求,第一種實(shí)現(xiàn)顯然更簡(jiǎn)單。

4. Observer 觀(guān)察者

作用

定義一個(gè)一對(duì)多關(guān)系,在Subject狀態(tài)發(fā)生改變時(shí),所有Observer獲得通知。

解耦 事件發(fā)生者 & 事件接收者,使得雙方的改動(dòng)互不影響,關(guān)聯(lián)關(guān)系也可動(dòng)態(tài)改變。

實(shí)現(xiàn)

觀(guān)察者模式

數(shù)據(jù)傳遞的兩種方式:

  1. 推:由 Subject 主動(dòng)向 Observer 推送信息,而不管信息對(duì)后者而言是否需要/是否足夠。
  2. 拉:Subject 把自己傳遞給 Observer,由 Observer 從 Subject 拉取自己需要的信息。

擴(kuò)展: Observer 注冊(cè)時(shí)可以指定自己感興趣的事件。

擴(kuò)展:EventBus

傳統(tǒng)的Observer模式中,事件發(fā)生者和接收者依然存在耦合,發(fā)生者需要管理接收者的集合,我們可以進(jìn)一步地,在Subject和Observer間增加一個(gè)中間層負(fù)責(zé)轉(zhuǎn)發(fā)事件,將它們徹底地解耦;進(jìn)一步,這個(gè)事件轉(zhuǎn)發(fā)者可以是通用的,支持任意發(fā)布者和接受者,通常稱(chēng)之為EventBus,是一種廣泛應(yīng)用的架構(gòu)。

5. State 狀態(tài)模式

作用

模擬狀態(tài)機(jī),描述一個(gè)對(duì)象(Context)的狀態(tài)變遷,將特性狀態(tài)下的行為分割開(kāi)來(lái),避免在Context中用大量的if維護(hù)所有狀態(tài)的變遷,而且容易擴(kuò)展新的狀態(tài)。

實(shí)現(xiàn)

狀態(tài)模式
  1. Context中記錄它自己當(dāng)前的狀態(tài);
  2. Context接收一個(gè)輸入動(dòng)作,并將該輸入委托給當(dāng)前所處State處理;
  3. State處理輸入,根據(jù)需要讓Context躍遷到另一狀態(tài)。

如果State不保存狀態(tài)則可以是單例的,Java中,可以用enum類(lèi)型實(shí)現(xiàn)State

6. Strategy 策略模式

作用

你有一個(gè)對(duì)象負(fù)責(zé)完成某件事情,但在不同時(shí)刻其使用的算法是不同的,Strategy模式將可變的算法獨(dú)立并封裝,避免大量if條件判斷,并方便替換和擴(kuò)展。

Strategy 封裝了 相同行為的不同實(shí)現(xiàn)

實(shí)現(xiàn)

策略模式

Strategy的實(shí)現(xiàn)通常依賴(lài)Context的數(shù)據(jù),后者在調(diào)用前者的方法時(shí)需要將自己傳遞過(guò)去。

實(shí)際應(yīng)用中,經(jīng)常會(huì)發(fā)現(xiàn)不同的策略其算法骨架類(lèi)似,只有某些具體步驟不同,此時(shí)可以對(duì)Strategy應(yīng)用Template Method模式。

7. Template Method 模板方法

作用

將一個(gè)算法的通用骨架抽象到父類(lèi)以避免代碼重復(fù),而將一些可變的步驟延遲到子類(lèi),子類(lèi)不用關(guān)心算法結(jié)構(gòu),只需關(guān)注自己需要實(shí)現(xiàn)的特定步驟。

實(shí)現(xiàn)

模板方法

這個(gè)沒(méi)什么好說(shuō)的。


四. 設(shè)計(jì)原則

  1. SRP原則 - 單一職責(zé)原則(Single Responsibility Principle)

    每個(gè)類(lèi)實(shí)現(xiàn)單一職責(zé),并且單一職責(zé)都有清楚明確的定義,有利于降低軟件的復(fù)雜性, 提高可讀性、可維護(hù)性和可擴(kuò)展性。

    軟件設(shè)計(jì)應(yīng)遵守單一職責(zé)原則,將不同的職責(zé)封裝到不同的類(lèi)或模塊中。

  2. OCP法則 - 開(kāi)放-關(guān)閉原則

    一個(gè)類(lèi)應(yīng)當(dāng)對(duì)擴(kuò)展開(kāi)放,對(duì)修改關(guān)閉。即當(dāng)有新的需求時(shí),不是修改已有的類(lèi),而是對(duì)已有的類(lèi)進(jìn)行擴(kuò)展。

    • 通過(guò)擴(kuò)展已有軟件系統(tǒng),可以提供新的行為,以滿(mǎn)足對(duì)軟件的新的需求,使變化中的軟件有一定的適應(yīng)性和靈活性。
    • 已有軟件模塊,特別是最重要的抽象層模塊不能再修改,這使變化中的軟件系統(tǒng)有一定的穩(wěn)定性和延續(xù)性。
      實(shí)現(xiàn)開(kāi)閉原則的關(guān)鍵在于 分離不變和變化的部分,并對(duì)變化的部分進(jìn)行合理的高層抽象,并讓不變的部分依賴(lài)該高層抽象,這樣就能在不同的實(shí)現(xiàn)間切換,或者擴(kuò)展新的實(shí)現(xiàn)。很多設(shè)計(jì)模式都體現(xiàn)了這一點(diǎn),比如策略模式將算法抽象出來(lái),模板方法將不變的算法骨架與易變的需要自定義的步驟隔離,裝飾者模式將不變的核心功能對(duì)象和易變的增強(qiáng)功能隔離等等。
  3. LSP法則 - 里氏替換原則(Liskov Substitution Principle)
    把抽象接口和實(shí)現(xiàn)分離,子類(lèi)必須能替換掉父類(lèi),這個(gè)原則通常由語(yǔ)言保證。

  4. DIP法則 - 依賴(lài)倒置原則(Dependence Inversion Principle)
    抽象不應(yīng)當(dāng)依賴(lài)于細(xì)節(jié);細(xì)節(jié)應(yīng)當(dāng)依賴(lài)于抽象;要針對(duì)接口編程,不針對(duì)實(shí)現(xiàn)編程。具體講就是要依賴(lài)于抽象,不要依賴(lài)于具體。
    高層不直接依賴(lài)底層,而是高層定義自己需要底層提供什么樣的接口,底層負(fù)責(zé)實(shí)現(xiàn),這樣就可以隨意切換底層的具體實(shí)現(xiàn)而不用影響高層,但底層反而要依賴(lài)高層公布的接口,所以稱(chēng)為“依賴(lài)倒置”。

  5. ISP原則 - 接口分離原則 (Interface Segregation Principle)
    每一個(gè)接口應(yīng)該是一種角色,不多不少,不干不該干的事,該干的事都要干。這類(lèi)似編碼原則中的最小權(quán)限法則。軟件設(shè)計(jì)不應(yīng)出現(xiàn)龐大的接口,迫使客戶(hù)在使用時(shí)必須從一大堆它不需要的方法中尋找目的方法。這樣的接口應(yīng)該按照不同客戶(hù)的需求被分離成若干小接口。

  6. LKP原則 - 最少知識(shí)原則(Least Knowledge Principle)

    也稱(chēng)為迪米特法則。原則的核心思想即一個(gè)對(duì)象應(yīng)對(duì)其他對(duì)象有盡可能少的了解。

    類(lèi)應(yīng)當(dāng)只與自己的朋友交互。該原則的思想是,將類(lèi)對(duì)外部的了解盡量保持在一定范圍內(nèi),盡量減少類(lèi)之間的交互,從而降低各個(gè)組件間的耦合。

    “朋友”的定義:

    • 當(dāng)前對(duì)象的屬性
    • 當(dāng)前對(duì)象所創(chuàng)建的對(duì)象
    • 方法參數(shù)傳遞進(jìn)來(lái)的參數(shù)
    • 方法內(nèi)創(chuàng)建的對(duì)象

五. 參考與推薦

  • 《大話(huà)設(shè)計(jì)模式》
  • 《深入淺出設(shè)計(jì)模式》
  • 《Java與模式》
  • 《敏捷軟件開(kāi)發(fā)原則、模式與實(shí)踐》
  • 《設(shè)計(jì)模式--可復(fù)用面向?qū)ο筌浖幕A(chǔ)》
  • 《設(shè)計(jì)模式精解》
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 一、設(shè)計(jì)模式的分類(lèi) 總體來(lái)說(shuō)設(shè)計(jì)模式分為三大類(lèi): 創(chuàng)建型模式,共五種:工廠(chǎng)方法模式、抽象工廠(chǎng)模式、單例模式、建造者...
    AI喬治閱讀 1,638評(píng)論 0 2
  • 本文是《設(shè)計(jì)模式——可復(fù)用面對(duì)對(duì)象軟件的基礎(chǔ)》的筆記。 面對(duì)對(duì)象設(shè)計(jì)的幾個(gè)原則:1.針對(duì)接口編程,而不是針對(duì)實(shí)現(xiàn)編...
    Lension閱讀 1,365評(píng)論 0 0
  • 設(shè)計(jì)模式匯總 一、基礎(chǔ)知識(shí) 1. 設(shè)計(jì)模式概述 定義:設(shè)計(jì)模式(Design Pattern)是一套被反復(fù)使用、多...
    MinoyJet閱讀 4,093評(píng)論 1 15
  • 一、設(shè)計(jì)模式的分類(lèi) 總體來(lái)說(shuō)設(shè)計(jì)模式分為三大類(lèi): 創(chuàng)建型模式,共五種:工廠(chǎng)方法模式、抽象工廠(chǎng)模式、單例模式、建造者...
    lichengjin閱讀 999評(píng)論 0 8
  • 10月13日電 據(jù)諾貝爾獎(jiǎng)官網(wǎng)最新消息,瑞典斯德哥爾摩當(dāng)?shù)貢r(shí)間13日下午1時(shí),2016年諾貝爾文學(xué)獎(jiǎng)揭曉,美國(guó)民謠...
    飛春讀傳閱讀 536評(píng)論 0 3

友情鏈接更多精彩內(nèi)容