1. 程式人生 > 其它 >設計模式讀書筆記01

設計模式讀書筆記01

技術標籤:設計模式

軟體建模與設計過程可以拆分**成需求分析、概要設計和詳細設計(類圖)**三個階段。UML 規範包含了十多種模型圖,常用的有 7 種:類圖、序列圖、元件圖、部署圖、用例圖、狀態圖和活動圖(UML中沒有流程圖,用這個代替)

類以及類之間的關係:。類之間有 6 種靜態關係:關聯、依賴、組合、聚合、繼承、泛化。

主要是通過用例圖來描述系統的功能與使用場景

對於關鍵的業務流程,可以通過活動圖描述

程式設計師的差距一方面體現在程式設計能力、另一方面體現在程式設計方面,好的設計和壞的設計最大的差別就體現在應對需求變更的能力上

一個設計腐壞的例子,例如輸入、輸出。;各種輸入裝置、輸出裝置。----面向介面變成

我們在**開始設計的時候就需要考慮程式如何應對需求變更,並因此指導自己進行軟體設計,在開發過程中,需要敏銳地察覺到哪些地方正在變得腐壞,然後用設計原則去判斷問題是什麼,再用設計模式去重構程式碼解決問題**。

軟體設計的開(拓展)閉(關閉修改)原則:如何不修改程式碼卻能實現需求變更?

開閉原則說:軟體實體(模組、類、函式等等)應該對擴充套件是開放的,對修改是關閉的。當需求變更時拓展

以通過按鈕撥號的電話,0~9,增加按鈕型別時,,,似乎對 Button 類做任何的功能擴充套件,都要修改 Button 類

粗暴一點說,當我們在程式碼中看到 else 或者 switch/case 關鍵字的時候,基本可以判斷違反開閉原則了


設計模式中很多模式其實都是用來解決軟體的擴充套件性問題的,也是符合開閉原則的。我們用策略模式對上面的例子重新進行設計。
策略模式是一種行為模式,多個策略(撥號器、密碼鎖)實現同一個策略介面,ButtonService程式設計的時候 client 程式依賴策略介面,執行期根據不同上下文向 client 程式傳入不同的策略實現。

利用策略模式來避免冗長的 if-else 或 switch 分支判斷,別的模式也能移除分支判斷。在工廠中查表法。策略模式例項,檔案排序:內排序、外部排序、多執行緒外部排序、利用MapReduce多機排序。

策略模式的步驟:1. 將策略的定義分離出來。—複用。每種排序類都是無狀態的–沒有必要每次使用時建立一個新的物件,使用工廠模式封裝。修改工廠,違背了開閉原則,此時通過反射讀取被 annotation 標註的策略類

策略工廠類讀取配置檔案或者搜尋被 annotation 標註的策略類,然後通過反射了動態地載入這些策略類、建立策略物件

介面卡模式是一種結構模式,用於將兩個不匹配的介面適配起來,使其能夠正常工作一個按鈕控制多個裝置----觀察者模式,監聽者介面,一對多的物件依賴關係

所謂模板方法模式,就是在父類中用抽象方法定義計算的骨架和過程,而抽象方法的實現則留在子類中。

實現開閉原則的關鍵是抽象,就是介面。依賴介面–就可以隨意對這個抽象介面進行拓展,這個時候不需要對現有程式碼進行任何修改,利用介面的多型性,通過增加一個新的實現該介面的類,就能完成需求變更。

當需求變更的時候,現在的設計能否不修改程式碼就可以實現功能的擴充套件?如果不是,那麼就應該進一步使用其他的設計原則和設計模式去重新設計。
關鍵還是看場景

軟體設計的依賴倒置原則:如何不依賴程式碼卻可以複用它的功能?介面的所有權是被倒置的

高層模組不應該依賴低層模組,二者都應該依賴抽象。抽象不應該依賴具體實現,具體實現應該依賴抽象

依賴倒置原則的應用:1.JDBC,2.J2EE規範,不需要依賴Tomcat這種web容器。在框架設計時被用的很多,

利用依賴倒置的設計原則,每個高層模組都為它所需要的服務宣告一個抽象介面,而低層模組則實現這些抽象介面,高層模組通過抽象介面使用低層模組而實現這一特性的前提就是應用程式必須實現框架的介面規範,比如實現 Servlet 介面
對具體類的繼承是一種強依賴關係,維護的時候難以改變

可以把介面看作一組抽象的約定

軟體設計的里氏替換原則(子型別必須能夠替換掉它們的基型別):正方形可以繼承長方形嗎?

絕大多數設計模式其實都是利用多型的特性玩的把戲

通俗地說就是:子型別必須能夠替換掉它們的基型別所有使用基類的地方,都應該可以用子類代替

例項,作為子類的白馬可以替換掉基類馬,但是小馬不能替換馬,因此小馬繼承馬就不太合適了,違反了里氏替換原則

我們判斷一個繼承是否合理?會使用“IS A”進行判斷,類 B 可以繼承類 A,我們就說類 B IS A 類 A,比如白馬 IS A 馬,轎車 IS A 車。
子類不能比父類更嚴格,否則替換時會因為更嚴格的契約而失敗,反例,在 JDK 中,類 Properties 繼承自類 Hashtable,類 Stack 繼承自 Vector。

實踐中,當你繼承一個父類僅僅是為了複用父類中的方法的時候,那麼很有可能你離錯誤的繼承已經不遠了。一個類如果不是為了被繼承而設計,那麼最好就不要繼承它。粗暴一點地說,如果不是抽象類或者介面,最好不要繼承它。如果你確實需要使用一個類的方法,最好的辦法是組合這個類而不是繼承這個類,這就是人們通常說的組合優於繼承

內聚性主要研究組成一個模組或者類的內部元素的功能相關性
一個類,應該只有一個引起它變化的原因。—單一職責,WEB框架的演進。

如何判斷一個類的職責是否單一,就是看**這個類是否只有一個引起它變化的原因**。

Rectangle 類的設計就違反了單一職責原則。Rectangle 承擔了兩個職責,一個是幾何形狀的計算,一個是在螢幕上繪製圖形。也就是說,Rectangle 類有兩個引起它變化的原因,這種不必要的耦合不僅會導致科學計算應用程式龐大,而且當圖形介面應用程式不得不修改 Rectangle 類的時候,還得重新編譯幾何計算應用程式。

軟體設計的介面隔離原則:如何對類的呼叫者隱藏類的公有方法–拆分介面,實現多個介面
介面隔離原則說:不應該強迫使用者依賴他們不需要的方法。

通過使用介面隔離原則,我們可以將一個實現類的不同方法包裝在不同的介面中對外暴露。應用程式只需要依賴它們需要的方法,而不會看到不需要的方法。
我們開發的絕大多數軟體都是用來解決現實問題的

軟體建模比較知名的是 4+1 檢視模型,即建模方法論。

針對具體的用例場景,領域,將上述 4 個檢視關聯起來,一方面從業務角度描述,功能流程如何完成,一方面從軟體角度描述,相關組成部分如何互相依賴、呼叫。

領域驅動設計:業務邏輯圍繞領域模型設計,主要是物件的設計,包含領域相關知識,而不是隻有getter,setter等

如果你對自己要開發的**業務領域沒有清晰的定義和邊界,沒有設計系統的領域模型,而僅僅跟著所謂的需求不斷開發功能,一旦需求來自多個方面,就可能發生需求衝突**,這個需求可能是偽需求
領域模型是合併了行為和資料的領域的物件模型

如何用領域模型模式設計一個完整而複雜的系統,有沒有完整的方法和過程指導整個系統的設計?領域驅動設計,即 DDD 就是用來解決這一問題的。

實體設計是 DDD 的核心所在,首先通過業務分析,識別出實體物件,然後通過相關的業務邏輯設計實體的屬性和方法。這裡最重要的,是要把握住實體的特徵是什麼,實體應該承擔什麼職責,不應該承擔什麼職責,分析的時候要放在業務場景和界限上下文中,而不是想當然地認為這樣的實體就應該承擔這樣的角色。

設計模式的精髓在於對面向物件程式設計特性之一——多型的靈活應用,而多型正是面向物件程式設計的本質所在。
多型的好處:軟體程式設計時的實現無關性,程式針對介面和抽象類程式設計,而不需要關心具體實現是什麼
設計模式:模式是**可重複的解決方案**

裝飾模式最大的特點是,通過類的建構函式傳入一個同類物件,OR介面,也就是每個類實現的介面和建構函式傳入的物件是同一個介面。設計模式是一個非常注重實踐的程式設計技能。模板和策略

設計模式應用:程式設計框架中的設計模式

框架通常規定了一個軟體的主體結構

當你設計一個框架的時候,你實際上是在設計一類軟體的通用架構,並通過程式碼的方式實現出來。如果僅僅是提供功能介面供程式呼叫,是無法支撐起軟體的架構的,也無法規範軟體的結構。
Servlet實際上是一個介面。

JUnit 是一個 Java 單元測試框架,開發者只需要繼承 JUnit 的 TestCase,開發自己的測試用例類,通過 JUnit 框架執行測試,就得到測試結果。
當我們從樹的根節點遍歷樹,就可以執行所有這些測試用例。傳統上進行樹的遍歷需要遞迴程式設計的,而使用組合模式,無需遞迴也可以遍歷樹

反應式程式設計框架設計:如何使程式呼叫不阻塞等待,立即響應?–非同步–回撥

軟體設計的核心目標就是高內聚、低耦合

元件是軟體複用和釋出的最小粒度軟體單元

The default value of the class path is “.”, meaning that only the current directory is searched.

為何說要多用組合少用繼承?如何決定該用組合還是繼承?

類的繼承層次會越來越深、繼承關係會越來越複雜,會導致:1.可讀性差,搞清楚類的用途可能需要追溯父類,2.將父類的實現細節暴露給了子類,高度耦合,父類程式碼的修改,就會影響所有子類的邏輯。

public class Ostrich implements Tweetable, EggLayable {// 鴕鳥
    private TweetAbility tweetAbility = new TweetAbility(); // 組合
    private EggLayAbility eggLayAbility = new EggLayAbility(); // 組合
    //... 省略其他屬性和方法...
    @Override
    public void tweet() {
        tweetAbility.tweet(); // 委託
    }
    @Override
    public void layEgg() {
        eggLayAbility.layEgg(); // 委託
    }
}

繼承主要有三個作用:表示 is-a 關係支援多型特性程式碼複用

比如 is-a 關係,我們可以通過組合和介面的 has-a 關係來替代多型特性我們可以利用介面來實現程式碼複用我們可以通過組合和委託來實現。所以,從理論上講,通過組合、介面、委託三個技術手段,我們完全可以替換掉繼承,在專案中不用或者少用繼承關係,特別是一些複雜的繼承關係。
僅僅為了程式碼複用,生硬地抽象出一個父類出來,會影響到程式碼的可讀性,做法:組合,即成員變數是另一個類。

Repository 層負責資料訪問,Service 層負責業務邏輯,Controller 層負責暴露介面

public class UserBo {// 省略其他屬性、get/set/construct 方法
    private Long id;
    private String name;
    private String cellphone;
}//是一個儲存的資料結構,只包含資料,不包含任何業務邏輯,業務邏輯在UserService類中

像 UserBo 這樣,只包含資料,不包含業務邏輯的類,就叫作貧血模型(Anemic Domain Model)。

在貧血模型中,資料和業務邏輯被分割到不同的類中。充血模型(Rich Domain Model)正好相反,資料和對應的業務邏輯被封裝到同一個類中。因此,這種充血模型滿足面向物件的封裝特性,是典型的面向物件程式設計風格。

DDD是伴隨著微服務而盛行的。微服務還有另外一個更加重要的工作,那就是針對公司的業務,合理地做微服務拆分。而領域驅動設計恰好就是用來指導劃分服務的。所以,微服務加速了領域驅動設計的盛行。
做好領域驅動設計的關鍵是,看你對自己所做業務的熟悉程度,而並不是對領域驅動設計這個概念本身的掌握程度。即便你對領域驅動搞得再清楚,但是對業務不熟悉,也並不一定能做出合理的領域設計。所以,不要把領
域驅動設計當銀彈,不要花太多的時間去過度地研究它

​ 實際上,基於充血模型的 DDD 開發模式實現的程式碼,也是按照 MVC 三層架構分層的。Controller 層還是負責暴露介面,Repository 層還是負責資料存取,Service 層負責核心業務邏輯。它跟基於貧血模型的傳統開發模式的區別主要在 Service 層

我們先來回憶一下,**我們平時基於貧血模型的傳統的開發模式,都是怎麼實現一個功能需求的。**不誇張地講,我們平時的開發,大部分都是 SQL 驅動(SQL-Driven)的開發模式,—長得差不多、區別很小的SQL語句滿天飛。

在這種開發模式(DDD)下,我們需要事先理清楚所有的業務,定義領域模型所包含的屬性和方法。領域模型相當於可複用的業務中間層。新功能需求的開發,都基於之前定義好的這些領域模型來完成,不會出現需求保證的情況

簡單系統(不太關注可複用性)用貧血模型,複雜系統用,基於充血模型的 DDD 開發模式

因為交易流水有兩個功能:一個是業務功能,比如,提供使用者查詢交易流水資訊;另一個是非業務功能,保證資料的一致性。這裡主要是指支付操作資料的一致性。

不涉及複雜業務概念,職責單一、功能通用。

並且將原來在 Service 類中的部分業務邏輯移動到 VirtualWallet 類(領域物件)中,讓 Service 類的實現依賴 VirtualWallet 類。

理解OOP,我們就不難理解DDD:
DDD第一原則:將資料和操作結合。(貧血模型將資料和操作分離,違反OOP的原則。)
DDD第二原則:界限上下文。這是將“單一指責”應用於我們的領域模型。
DDD is nothing more than OOP applied to business models. DDD其實就是把OOP…