owned this note
owned this note
Published
Linked with GitHub
# 設計模式筆記
###### tags: `觀念`
## 如何分辨設計模式
您要花多久,才能分出 Class Diagram 哪個是 Proxy,哪個是 Decorator 呢?
從 Solution 來分辯 Design Pattern 是沒有意義的行為;唯有從 Problem 下手分析,Design Pattern 才有價值。
來看看 Problem:
Proxy: you have a massive object that consumes a vast amount of system resources. You need it from time to time, but not always.
Decorator: you attach new behaviors to objects by placing these objects inside special wrapper objects that contain the behaviors.
一樣面對變化,一個是為了解決高成本資源載入的問題,一個是為了解決需要動態為一介面增加新行為的問題。
https://medium.com/kuma%E8%80%81%E5%B8%AB%E7%9A%84%E8%BB%9F%E9%AB%94%E5%B7%A5%E7%A8%8B%E6%95%99%E5%AE%A4/%E4%B8%80%E6%A8%A3%E4%B9%9F%E4%B8%8D%E4%B8%80%E6%A8%A3-%E4%BB%A3%E7%90%86-vs-%E8%A3%9D%E9%A3%BE%E8%80%85-661eb941a2fa
## bottom-up 無法得到優美設計
“將預先形成的部分加起來是無法獲得優秀的軟體設計的。
”在軟體行業中,所謂“將預先形成的部分加起來”,就是說先創建一個副程序庫,然後再將它們組合起來,建構出系統。
從部分構造整體,不可能得到優美的設計。
當各個部分都是自訂的、並且在形成整體之前已經產生時。
每個部分不可能只根據自己在整體中的位置而具有獨特性。
一開始我對亞歷山大關於建模的論證並不理解。按道理要實現重用,必須給出一些通用的例程,但他卻在說不這樣做。
後來,我認識到,稱為“建模” ,Alexander是指在建築業中那些幾個、可以形成互換的部分,他不是指我們軟體產業中所使用的術語「模組」。他的意思是,如果在總體概念之前就開始建立模組,這些模組不可能有能力處理特殊的需求。
亞歷山大斷言:設計應該從問題的一個簡單陳述開始,然後通過在這個陳述中添加信息,從而變得更加詳細(也更加複雜)。
開始用最簡單的術語來考慮問題(概念性層次),然後添加更多功能,這個過程中設計將變得越來越複雜,因為我們加入了越越來越多的信息。
例如,假設需要為一個有 40 位聽眾的演講安排房間。當你向別人描述需求時,可能會這樣說:「我需要一間房間30英尺長30英尺寬的房間」(開始很簡單)。然後是:「我想把椅子按劇院的方式安排:4行,每行8把」(添加信息,使房間的描述更加複雜)。接下來是:「房間的前面需要放一個講台」(更複雜)。
ref: 設計模式解析(第2版修訂版 第12章
## 共通性分析 和 可變性分析
共性分析尋找的是不可能隨時間而改變的結構,而可變性分析則要找到可能變化的結構。
可變性分析只有在相關聯的共性分析定義的上下文中才有意義。
共性分析為架構提供長效的要素,而可變性分析則促進其適應實際使用需求。
如果變化是問題領域中各個特定的具體情況,共性就定義了問題領域中將這些情況聯繫起來的概念。
例如,如果我向你展示一支白板記號筆、一支鉛筆和原子筆一樣,你會說它們都有一個共同點就是它們都是「書寫工具」。這裡你所做的識別其共同點的過程就是共性分析。
![image](https://hackmd.io/_uploads/SJBQ6Z8cp.png)
從圖可以看出,共性分析與問題領域的概念觀點是相互關聯的,而可變性分析與(特定情況的)實現是相互關聯的。
![image](https://hackmd.io/_uploads/B1fyA-I56.png)
使用共性和可變性分析來尋找對象,比尋找名詞和相應的動作更有效。
ref: 設計模式解析(第2版修訂版 第八章
---
共性和可變性分析可以出概念視角(共通性)和現實視角(具體的變化)。
如果只考慮共通性和使用共通性的對象,可以以另一種方式思考問題-分解責任。
ref: 設計模式解析(第2版修訂版 第25章
## 分析矩陣
利用分析矩陣
尋找 共通性分析 和 可變性分析
客戶對於回答具體問題是非常熟練的,而對於“還有其他什麼嗎?」
這樣的問題,他們一般都不擅長。
一般情況下,他們不會像開發人員經常那樣在概念層面表達事情。恰恰相反,他們得非常具體。
![image](https://hackmd.io/_uploads/r1JDK_w5a.png)
ref: 設計模式解析(第2版修訂版 第16章
## 設計模式重點
如何在不重新設計的情況下
進行改變
用物件的職責,而不是其結構來思考問題
避免繼承的原因:
避免封裝一個有兩個要變化的事物,除非這些變化明顯地連接在一起
設計程序時應該按照這樣的方式進行:首先,使用CVA建議找到問題域中存在的各種概念(共性)和具體的實現(可變性)。
接下來我們最感興趣的是找到其中的概念,但是這個過程中也發現了許多可變性。
將繼承看成一種將變化概念化的方法,而不是創建現有物件的特殊情況。
## uml
![image](https://hackmd.io/_uploads/BJnZZ2Q_a.png)
![image](https://hackmd.io/_uploads/S1bIzhmu6.png)
聚合:
被包含者是包含者的一部分(比如汽车中的发动机)
組合:
有一个集合,集合中东西可以独立存在(比如机场上的飞机)
## 责任链模式 如何設計 next 方法
```go
// gin
func (c *Context) Next() {
c.index++
for c.index < int8(len(c.handlers)) {
c.handlers[c.index](c) //执行handler
c.index++
}
}
```
https://blog.dianduidian.com/post/gin-%E4%B8%AD%E9%97%B4%E4%BB%B6next%E6%96%B9%E6%B3%95%E5%8E%9F%E7%90%86%E8%A7%A3%E6%9E%90/
---
![](https://hackmd.io/_uploads/B1Qb5OPf6.png)
![](https://hackmd.io/_uploads/BJB7FuwzT.png)
处理者可以决定不再沿着链传递请求, 这可高效地取消所有后续处理步骤。
https://refactoringguru.cn/design-patterns/chain-of-responsibility
## api first
* 手上先有規格 or low fidelity design
* 研判這個需求大小, 複雜度如何
* 2.a 如果這個需求是 complicated, 會盡量多開一些協作的票,OOAD/ES/design grooming 之類的
* 2.b 如果是 complex, chaotic,開研究的票
* 除非是 obvious 的工作外,花半天的時間寫實作提案,給比我資深的人或專家 review
* 3.a 提案要細要粗自己決定,可以包含 problem statement, 重要的需求, ERD, UML 或流程圖,API spec 也可以包含在那
* 不一定要 TDD, 但先想好測試案例,再開始寫
https://discord.com/channels/937992003415838761/1155805257759203379/1155805257759203379
## ooad 分析
![](https://hackmd.io/_uploads/BkHNPYo63.png)
![](https://hackmd.io/_uploads/ByDSvFiTh.png)
![](https://hackmd.io/_uploads/SkjLvtjph.png)
![](https://hackmd.io/_uploads/ByqDPFi62.png)
![](https://hackmd.io/_uploads/r1MWKto62.png)
https://miro.com/app/board/uXjVMMo-3GY=/
## 繼承注意事項
你以為你在使用繼承來達成 separation of concerns 嗎?不,你只是用它來把一個巨大的 class 寫在不同 files 而已
https://www.facebook.com/mnf.shih/posts/2024293847588837
重要的是訊息傳遞(messaging),物件只是次要的概念
通常都只是繼承一個abstract class,極少出現第二次繼承,不然會變得太複雜
1-2層的繼承我覺得還可以接受,太多層會不容易理解
https://www.facebook.com/groups/teddy.tw/posts/4704496679617385/
## 多型 oop 想減少什麼樣類型的程式碼
一個訊息(message or event or stimulus)的意義是由接收者(接收到這個訊息的物件)來解釋,而不是由訊息發出者(sender)來解釋。
所以,在runtime時只要接受者換成不同的物件或是instance,系統的行為就會改變。具有這樣的特性就稱之為polymorphism
http://teddy-chen-tw.blogspot.com/2012/01/3polymorphism.html
最應該要 reuse 的應該是商業
有時代碼重複看起來像樣板文件,但它是對抗代碼耦合的最佳工具之一。
DRY 更好地應用於行為(流程?),而不是數據。
https://threedots.tech/post/things-to-know-about-dry/
## 中介者模式 Mediator Pattern
物件間相互直接的引用,造成維護困難
![](https://i.imgur.com/QGiVshh.png)
加入中介者則會變成,星狀結構
藉由避免物件間相互直接的引用,從而降低它們之間的耦合程度
![](https://i.imgur.com/6tkvUMO.png)
使用場景
當物件出現蜘蛛網狀結構。在這種情況下,一定要考慮使用中介者模式。這有利於把蜘蛛網梳理
為星型結構,使原本複雜混亂的關閉變得清晰簡單。
缺點
中介者模式的缺點就是中介者會膨脹得很大,而且邏輯複雜
https://www.dotblogs.com.tw/bda605/2020/08/15/204020
## 訪問者模式 (Visitor Pattern)
將類別的行為和類別切開
(可以思考 物件 和 資料 的 差異)
可以想成, 如何用物件導向的形式, 達成 函數想做到的事情
表示一個作用於某物件結構中的各元素之操作。它使你可以再不改變各元素之類別的前提之下,定義作用於這些元素的新操作。
Represent an operation to be performed on elements of an object structure. Visitor lets you define a new operation without changing the classes of the elements on which it operates.
元素的個數是固定時(穩定的資料結構)
將處理和資料結構兩者分離開來。
增加操作就等於是增加新的Visitor。
https://coolshell.cn/articles/21263.html
https://ithelp.ithome.com.tw/articles/10208766
物件本身的行為, 不由自己決定, 由外部決定
```
// 元件類別都要實作的介面,目的是取得 visitor,
// 將實際上要做的事透過 visitor 委派
public interface CarElement
{
public void accept(final CarElementVisitor visitor);
}
```
特定的操作(邏輯行為)可以包裝在訪問者裡,因此使用訪問者模式,我們可以藉由新增具象訪問者來達到新的操作。因為這個模式容易增加新的操作,且不會影響元件類別,也符合開放封閉守則。但也可以發現,一旦我們需要加新的元件或是修改特定元件類別時,整個架構要改動的地方就會很多,因此訪問者模式不適合用在元件需要經常更動的情形。
http://corrupt003-design-pattern.blogspot.com/2017/02/visitor-pattern.html
## Bridge Pattern
《設計模式》一書中對橋接模式的解釋是這樣敘述的:
「將抽象與實現解耦,使它們都可以獨立地變化」
然能聽懂這句話的每個字,但卻對整個句子的意思頭緒,這是怎麼回事呢?
因為誤解了實現的含意。這裡的實作是指
使用抽象類別及其衍生類別用來實作自己的物件
(而不是抽象類別的衍生類別,這些衍生類別稱為具體類別)
對 Bridge 模式的迷惑是由於我對術語「實現」的誤解。
錯認「實現」是指「如何實現一個特定的抽象」。
思考曾經遇到的類似情況,從而具有以下特點:
* 概念的抽像有變化
* 這些概念的實現方式都有變化
“組合爆炸”問題
抽象(形狀的種類)與其實現(繪圖程序)是緊密相連的。
需要有一種方法將抽像上的變化和實現上的變化分開,從而使類別的數量線性增加
當一個抽象存在不同的實作時,Bridge 模式幾乎都是有用的,它可以使抽象和實作相互獨立地進行變更。
在創建物件時使用共性和可變性分析作為主要工具
首先,找到發生變化的內容,再尋找共同處
變化是形狀的種類和繪圖程序的種類
共同的概念是“形狀”和“繪圖程序”
透過讓類別A使用另一個類別B,將這些類別連結起來
問題是如何決定主從關係呢?
ref: 設計模式解析(第2版修訂版 第10章
---
橋接想解決的問題是「多種」不同層次概念獨立變化的問題
橋接模式 Bridge Pattern 及其欲解決的 Problem:「你的類別中浮現出了兩種不同抽象層次,『高階』和『低階』兩個部分嗎!?而你想要解耦它們以致於彼此能獨立改變並且允許各種組合嗎!?」
**初版的類別圖**
![image.png](https://hackmd.io/_uploads/SyBp8cNQp.png)
早餐店的菜單設計就會面臨著以下幾道 Forces
1. 要允許每一種搭配組合
2. 不知道如何表達在菜單中傳達,有太多組合了
3. 多搭配組合之下,錢有點難算啊,得制定好定價的規則
軟體設計的角度來描述這三道 Forces 的話,可以寫出以下:
1. 結構變動性 (Structural Variation):單品類別中存在著兩個不同抽象層次的獨立變化——是某種味道落在了某種口感上。
1. 程式維護性的要求 (Code Maintainability’s Requirement) :組合爆炸 (Combinatorial Explosion)!組合的數量太多,如果用繼承來表示每一種組合的話,則會有太多的子類別要維護。
1. 定價的行為變動性 (Pricing Behavioral Variation):根據不同的單品組合,會有不同的定價行為。
依據 Force 3,我們確信了「價格」不會只是屬性,因為定價會根據組合的不同而有所變動,而在這麼多種組合之下,未來肯定也會有不同的定價策略。因此,我們該將定價視為是一個行為
**提出需求後的類別圖**
![image.png](https://hackmd.io/_uploads/Skf6vcVXp.png)
橋接模式的 Form 如下圖:
![image.png](https://hackmd.io/_uploads/SJIVOcNm6.png)
Redesign:
![image.png](https://hackmd.io/_uploads/rkpBd9VQ6.png)
[軟體設計模式的早餐店菜單](https://medium.com/@waterball.tw/%E9%80%99%E4%BB%BD%E5%A5%97%E7%94%A8%E4%BA%86-gof-%E8%BB%9F%E9%AB%94%E8%A8%AD%E8%A8%88%E6%A8%A1%E5%BC%8F%E7%9A%84%E6%97%A9%E9%A4%90%E5%BA%97%E8%8F%9C%E5%96%AE-40910416f954)
---
將抽象部分與實現部分分離,使它們都可以獨立的變化。
橋接模式(Bridge Pattern)是用合成關係代替繼承關係,進而降低抽象和實作的合度。
優點
1. 抽象和實現的分離,提高了比繼承更好的解決方案;
2. 優秀的擴充套件能力,在兩個變化維度中任意擴充套件一個維度,都不需要修改原有系統;
3. 實現細節對客戶透明,可以對使用者隱藏實現細節。
缺點
增加系統的理解與設計難度,由於合成關聯關係建立在抽象層,要求開發者針對抽象進行設計與程式設計。
https://ianjustin39.github.io/ianlife/design-pattern/bridge-pattern/
## 抽象工廠 - 產品族
產品等級結構:產品的繼承結構。比如一個抽象類是麵糰,子類別有 Chicago 麵團跟 NY 麵團,這三個形成了一個產品等級結構。
產品族:同一個工廠生產的所有產品,其中的每個產品都是座落在不同的產品等級結構中的其中一個產品。
相關的產品(NY 麵團工廠、NY 醬料工廠、NY 起司工廠、NY 蛤蠣工廠)組成一個產品族,交給同一個工廠來生產
https://blog.techbridge.cc/2017/05/22/factory-method-and-abstract-factory/
## 工廠方法
工廠方法模式定義了一個建立物件的介面,但由子類決定要實例化的類別為何。
工廠方法讓類別把 實例化 的動作推遲到了子類。
為了decouple物件的創造和物件的使用,製造了一個工廠。
可是為了 reuse 工廠的 code,我們使用了繼承。
![](https://i.imgur.com/5zAZ0vL.png)
![](https://i.imgur.com/ZejgWRP.png)
工廠方法的 factoryMethod,只能創建一個對象,比如說 Pizza 。
但**如果我們想要更加細分想創建的東西**,比如說 Pizza 的所需原料(麵團、醬料、起司、蛤蠣)。如果我們用工廠方法的話,我們需要為每一個原料都創一個工廠。
在這個例子就是 NY 麵團工廠、NY 醬料工廠、NY 起司工廠、NY 蛤蠣工廠、Chicago 麵團工廠、Chicago 醬料工廠、Chicago 起司工廠、Chicago 蛤蠣工廠。
每個工廠方法只能生產一個產品, 太難 maintain 了
https://blog.techbridge.cc/2017/05/22/factory-method-and-abstract-factory/
## 抽象工廠
![image](https://hackmd.io/_uploads/rJNprmP5a.png)
![image](https://hackmd.io/_uploads/r1eZ8mP9a.png)
《設計模式》一書的作者稱這個模式為抽象工廠呢?初看起來,很容易認為這是因為工廠是用的工廠情況為什麼有派生類的抽象類實現的。
但事實並非如此。這個模式之所以稱為抽象工廠,是因為它要創建的東西本身是由抽象定義的(在上例中是DisplayDriver 和PrintDriver)
ref: 設計模式解析(第2版修訂版)第11章
---
由於工廠方法只能產生單一產品(因為是實體, 不是抽象)
若想要細分產品內的的零件
就必須有大量元件工廠, 如上述的 麵團工廠、 醬料工廠
所以我們把相關的產品(NY 麵團工廠、NY 醬料工廠、NY 起司工廠、NY 蛤蠣工廠)組成一個產品族,交給同一個工廠來生產,鼎鼎大名的抽象工廠就誕生了。
將產品及元件 都**定義為抽象結構** abstract or interface
用一個抽象工廠來定義一個創建 產品族 的介面
產品族裡面每個**產品的具體**類別由繼承**抽象工廠的實體工廠**決定。
缺點非常致命,就是當我想在產品族加一個產品非常困難。
因為我所有子工廠要跟著改,這被稱為開閉原則的傾斜性:
新增產品族容易,但新增產品結構困難。
![](https://i.imgur.com/WR3vgiQ.png)
https://blog.techbridge.cc/2017/05/22/factory-method-and-abstract-factory/
## 產品族和產品等級結構
產品等級結構(產品的內部小元件):產品的繼承結構。比如一個抽象類是麵糰,子類別有 Chicago 麵團跟 NY 麵團,這三個形成了一個產品等級結構。
產品族:同一個工廠生產的所有產品,其中的每個產品都是座落在不同的產品等級結構中的其中一個產品。
![](https://i.imgur.com/ZK6Posc.png)
![](https://i.imgur.com/BlupgAz.png)
https://blog.techbridge.cc/2017/05/22/factory-method-and-abstract-factory/
## 建構與初始流程
新建物件與初始物件的流程應該分開看待
Java的建構式應該叫作初始式(Initializer),因為開發者無法決定如何新建(new)物件,Java實際上是新建物件之後,立即執行建構式中定義的初始流程
Java建構式中應當進行的動作是初始物件,而不是作初始物件前的前置資料準備動作。
https://openhome.cc/Gossip/Programmer/ConstructorToFactory.html
// 自我練習
// 比較 前置資料準備動作 的 執行位置
https://play.golang.org/p/5izwBKdoFM0
## 建構時, 傳遞多參數 作法
將多個參數 用 struct 包成新的結構
給予新的抽象意義
比如說 InputSeed 來表達 Input 組成的相關要素
## factory pattern 想解決的是兩件事:
1. 隱藏工廠回傳的物件是什麼實作
2. 隱藏工廠如何建立出實作的物件
為了達成第一點,所以必須要透過介面來包裝,讓實作們有共通的介面,使得呼叫工廠的人可以不用知道拿到的物件到底是什麼實作,但依然知道要如何操作物件。
然而,達成第二點其實才是大多數案例中真正想解決的問題。因為不同的實作的初始化方法、需要的資訊可能都是不同的,因此想要把這些複雜的初始化程序隱藏起來,讓呼叫物件的人不必知道這些物件到底如何被建立出來,因為這可能導致他需要知道太多跟他無關的資訊。
## solid
- SRP: 最小化功能, 藉由組合小功能, 完成大功能, 降低單一類別被「改變」的機會
- OCP: 限制新增修改時的影響範圍, 避免既有客戶端的程式碼被改變
- LSP: desing by contract, 重視合約規範, 要求元件的行為要一致
- ISP: 考慮不同客戶端的使用方式, 從客戶端需要什麼功能出發, 減少不必要的依賴
- DIP: 上層模組不依賴於下層模組, 方便替換實作, 讓系統易於擴展, 保護核心業務流程
[你真的懂 SOLID 原則嗎?](https://www.youtube.com/watch?v=Dmv6tMnaCQA&list=PLMe47YRNiTn32xNbX7M7efdjQhidwshOY&index=6)
---
![](https://i.imgur.com/1MJ4i3O.png)
![](https://i.imgur.com/dzsrJhy.png)
![](https://i.imgur.com/20wLRLe.png)
![](https://hackmd.io/_uploads/ry0yLFxtt.png)
![](https://i.imgur.com/hSGT2dr.png)
![](https://i.imgur.com/uLWxapM.png)
https://www.youtube.com/watch?v=e0UOuQ_lCUY
---
它們在談「面對原始碼改變的五種不同策略」,或是說「從五種不同的角度,來應付、管理原始碼改變」
* SRP:降低單一類別被「改變」所影響的機會,在《Refactoring: Improving the Design of Existing Code》這本書中所提到的Divergent Change(發散式變化)就是一種類別違反SRP所形成的壞味道,類別設計符合SRP便可避免Divergent Change。
* OCP:當新增需求的時候(功能變化),在不改變現有程式碼的前提之下,藉由增加新的程式碼來實作新的功能。
* LSP:透過繼承時子類別所造成的「行為變化」要如何設計,才可以讓程式在runtime透過多型所繫結(bind)到的子類別實作,不至於違反父類別介面的合約。
* ISP:針對不同的客戶端,只開放其所需要的介面讓它使用,如此一來可避免與降低客戶端因為不相關介面改變也一起被迫需要改變的問題。假設你有一個擁有10個函數的類別,現在有三個不同的客戶端都需要使用這個類別其中的4、7、5個函數。如果直接把這個類別傳給三個不同的客戶端,則這些客戶端很可能會因為它所沒使用到的函數改變了,也被迫跟著改變(因為原本類別的介面改變了)。另一種作法,則是針對三個不同的客戶端,提供三個不同的Adapter。透過Adapter,只開放客戶端所需的函數給它們,以隔離因為不相關介面改變所造成的客戶端改變。
* DIP:DIP談的是caller和callee之間的關係,在兩者之間增加一個(抽象)介面,避免caller(上層程式)因為callee(底層程式)改變而被迫改變。
主要是說上層模組不依賴於下層模組 兩者都應該依賴於抽象
https://teddy-chen-tw.blogspot.com/2014/04/solid.html?fbclid=IwAR2yNopgg8H2E1xTeZ7SbXhLdNvWGPYGqntmHmR7ugUbtepvJqcdi2wliqs
## PubSub vs observer
![](https://i.imgur.com/igjJmda.png)
在观察者模式中,观察者是知道Subject的,Subject一直保持对观察者进行记录。
然而,在发布订阅模式中,发布者和订阅者不知道对方的存在。它们只有通过消息代理进行通信。
观察者 模式需要在单个应用程序地址空间中实现,而发布-订阅更像交叉应用模式。
https://juejin.cn/post/6844903513009422343
---
golang observer 實做
https://levelup.gitconnected.com/the-observer-design-pattern-in-go-d564048fe9f6
---
主體 Subject,負責維護一個依賴項(稱之為觀察者 Observer)的列表,並且在狀態變化時自動通知它們。 該模式和發佈/訂閱模式非常相似(但不完全一樣)。
https://angular.tw/guide/observables
---
拥有一些值得关注的状态的对象通常被称为目标, 由于它要将自身的状态改变通知给其他对象, 我们也将其称为发布者 (publisher)。 所有希望关注发布者状态变化的其他对象被称为订阅者 (subscribers)。
https://refactoringguru.cn/design-patterns/observer
https://refactoringguru.cn/design-patterns/observer/go/example
## 消费模型
- 队列模式(点对点模式):多个消费者共同消费一个队列,每条消息只发送给一个消费者。
- 队列模式中多个消费者共同消费同一个队列,效率高。
- 发布/订阅模式:多个消费者订阅主题,每个消息会发布给所有的消费者。
- 一个消息可以被多次消费,能支持冗余的消费
(例如两个消费者共同消费一个消息,防止其中某个消费者挂了)
![消費模型比較](https://lotabout.me/2018/kafka-introduction/kafka-consumer-model.svg)
[Kafka 入门介绍](https://lotabout.me/2018/kafka-introduction/)
## 模板模式
https://play.golang.org/p/6NiW9ADmEgA
https://ithelp.ithome.com.tw/articles/10194505
## 筆記
- 建構物件 和 使用物件 不要集中在同一個 class (P58), 類似依賴注入的想法, 若建構和使用 都集中同一個 class 會造成耦合性太高
https://ithelp.ithome.com.tw/articles/10103342
- 儲存資料 和 遍歷資料 不要集中在同一個 class (P280)
- abstract class和interface都可以用來實現"design by contract"
https://jjnnykimo.pixnet.net/blog/post/21585257
- abstarct class在Java語言中體現了一種繼承關係,要想使得繼承關係合理,父類和派生類之間必須存在"is a"關係
- interface 來說則不然,並不要求interface的實現者和interface定義在概念本質上是一致的,僅僅是實現了interface定義的契約而已,是"like a"關係。
- 在 go 中 abstract 用 struct 來實現, 沒有遵守里式替換原則, 需要在 struct 內嵌 interface 才可以實現 abstract 功能
https://blog.51cto.com/liuxp0827/1353140
- 在面向对象编程中,子类应该是可以被当做父类来使用的。在里氏替换原则中,子类应该能在任何需要的地方替换掉父类,但是在 go 不行
- 設計套件時, 不要忘記 client, user 這些第三者的存在, 設計模式中兩者的互動, 要想到第三者使用時, 如何將兩者連結起來
- 「設計物件,應思考外部如何使用這個物件,而不是bottom-up的思考,這個物件要提供哪些功能給外面用」
## 動機 和 目的
![image](https://hackmd.io/_uploads/ByzrVFvca.png)
結構型:
如何將實作和抽象關聯
結構模型的作用將已有的功能組合起來。
行為型:
如何封裝變化
注意力應該放在動機上,行為型動機就是解耦。
主要目的是一個物件與另一個物件解耦。
此類模式的一個動機是允許伸縮或增加靈活性,我稱這一類模式為解耦型模式。
ref: 設計模式解析(第2版修訂版)第18章
---
行為型:
針對封裝行為變化
結構型:
將現有的程式碼整合到新的物件設計中
創建型:
管理物件的建立
定義新的物件時,我根據自己的需求定義它們,使用行為類型模式作為指導。
但是,當我將現有的物件加入新的設計中時此時,使用結構型模式作為指導最為適合。
ref: 設計模式解析(第2版修訂版)第20章
---
創建型模式:
動機:將物件的創建與使用分離,使系統獨立於如何創建、組合和表示物件。
目的:控制物件的創建過程,提高靈活性、重用性。
为其他两种模式使用提供了环境
---
結構型模式:
動機:管理類與類之間的組合關係,簡化系統架構,改善結構。
目的:確保不同實體之間的關係,降低整體系統的複雜度。
透過組合類別或是物件來產生更大的結構,用以適應更高層次的邏輯需求。
解决怎样组装现有的类,构建软件组件的解决方案。
解決軟體結構方面的問題,例如如何實現類之間的關聯、如何建立可擴展的物件結構等。
---
行為型模式:
動機:關注物件間的通信方式,降低耦合,支持擴展與重用。
目的:管理複雜的物件交互,分離算法和物件結構。
一组对等的对象怎样相互协作
以完成其中任何一个对象都无法单独完成的任务
解決軟體中對象之間互動和協調方面的問題
## 創建型模式
單例模式:
動機:確保某個類只有一個物件實例,提供一個訪問此物件的全域訪問點。
目的:控制物件創建數量,節省系統資源。
工廠方法模式:
動機:將物件的具體創建推遲到子類中,使系統無需關心物件創建細節。
目的:將物件的創建和使用分離,提高靈活性。
抽象工廠模式:
動機:提供創建一組相關或者相互依賴物件的接口,無需指定具體類別。
目的:封裝一組產品的創建,使系統獨立於具體產品創建。
建造者模式:
動機:將一個複雜物件的構建與其表示分離,使同樣的建造過程可以創建不同表示。
目的:將創建和表示分離,提高靈活性。
原型模式:
動機:通過複製現有物件來創建新物件,避免創建過程的高耗費。
目的:快速地生成大量物件,節省創建時的資源。
## 結構型模式
代理模式(Proxy):
動機:控制對其他物件的訪問。
目的:保護或優化對真實物件的訪問。
裝飾器模式(Decorator):
動機:動態給物件增加額外功能。
目的:擴展功能並保持單一職責原則。
外觀模式(Facade):
動機:為子系統提供簡單接口。
目的:降低使用複雜度,提高易用性。
享元模式(Flyweight):
動機:重用存在的相同或相似物件。
目的:減少物件數量,節省內存。
組合模式(Composite):
動機:以相同方式處理個別和組合物件。
目的:表示部分-整體層次結構。
橋接模式(Bridge):
動機:抽象與實現分離,獨立變化。
目的:提高系統擴展性。
適配器模式(Adapter):
動機:匹配兩個接口不兼容的類。
目的:使類可以一起工作。
閘道模式(Facade):
動機:為子系統提供統一接口。
目的:簡化客戶端使用子系統。
## 行為型模式
策略模式(Strategy):
動機:算法使用與實現分離。
目的:切換算法。
觀察者模式(Observer):
動機:建立一對多依賴關係。
目的:當物件狀態改變,通知依賴物件。
迭代器模式(Iterator):
動機:以一致方式訪問聚合物件。
目的:不暴露內部表示。
模板方法模式(Template Method):
動機:將不變與可變算法分離。
目的:提取公共行為,推遲可變到子類。
命令模式(Command):
動機:將請求封裝成物件。
目的:支持撤銷、記錄等。
狀態模式(State):
動機:物件內部狀態改變時改變行為。
目的:類看起來像改變了。
責任鏈模式(Chain of Responsibility):
動機:避免發送者與接收者耦合。
目的:讓多個物件處理請求。
中介者模式(Mediator):
動機:用中介物件封裝一系列物件交互。
目的:減少物件間依賴。
訪問者模式(Visitor):
動機:算法與物件結構分離。
目的:在不改變結構前提下新增操作。
解釋器模式(Interpreter):
動機:定義語言的語法與解釋器。
目的:提供程式間簡單的語言。
## 模式比較
### 装饰器模式 vs 责任链模式
责任链模式(Chain of Responsibility Pattern)和装饰器模式(Decorator Pattern)是两种不同的设计模式,它们具有不同的用途和结构。
1. **目的**:
- **责任链模式**的主要目的是在一系列对象中传递请求,直到找到一个能够处理请求的对象。这种模式用于解耦发送者和接收者,以便多个对象都有机会处理请求,而不需要显式指定接收者。责任链模式通常用于处理请求的层次结构。
- **装饰器模式**的主要目的是动态地为对象添加行为,而不需要修改其原始类。装饰器模式允许你透明地包装对象,以扩展或修改其功能。这种模式用于在运行时添加功能,而不是改变类结构。
2. **结构**:
- **责任链模式**通常涉及一系列处理请求的对象,每个对象都有一个指向下一个对象的引用。当一个对象无法处理请求时,它将请求传递给下一个对象,直到找到一个能够处理请求的对象。
- **装饰器模式**通常包括一个组件接口,具体组件,装饰器基类,具体装饰器等部分。装饰器通过嵌套或包装对象来扩展其功能。
3. **用途**:
- **责任链模式**通常用于处理复杂的请求处理逻辑,例如请求处理的层次结构,每个层次处理不同级别的请求。它可以用于创建灵活的处理管道。
- **装饰器模式**通常用于为单个对象透明地添加功能,而不需要修改该对象的类。这对于避免类爆炸(大量子类)非常有用,因为你可以将功能添加到对象而无需创建大量子类。
4. **关系**:
- **责任链模式**通常涉及多个协作的处理者对象,它们之间有一种"下一个处理者"的关系。
- **装饰器模式**涉及装饰器对象和被装饰的对象之间的关系,装饰器透明地包装被装饰的对象。
在总结上述差异后,责任链模式和装饰器模式是两种不同的设计模式,用于不同的情景和目的。责任链模式用于建立处理请求的对象链,而装饰器模式用于在运行时扩展对象的功能。
---
责任链模式適用的應用情境
* 當程序需要以不同方式處理不同類型的請求,且請求類型和順序事先未知時,可以使用責任鏈模式。
* 該模式能夠將多個處理者連接成一條鏈。接收到請求後,它會 "詢問" 每個處理者是否能夠處理該請求。這樣所有處理者都有機會來處理請求。
责任链模式、 命令模式、 中介者模式和观察者模式用于处理请求发送者和接收者之间的不同连接方式:
* 责任链按照顺序将请求动态传递给一系列的潜在接收者, 直至其中一名接收者对请求进行处理。
* 命令在发送者和请求者之间建立单向连接。
* 中介者清除了发送者和请求者之间的直接连接, 强制它们通过一个中介对象进行间接沟通。
* 观察者允许接收者动态地订阅或取消接收请求。
https://refactoringguru.cn/design-patterns/chain-of-responsibility
### Composite Pattern & Decorator Pattern
Decorator 呼叫順序的差別
```go
chain := HandlerChain{DecoratorA, DecoratorB, ErrorHandlerDecorator}
// The principle of HandlerChain primarily relies on reverseApplyHandler
chain.reverseApplyHandler(ProduceError)(nil, "reverse apply use case")
// Before Decorator A1: reverse apply use case
// Before Decorator B1: reverse apply use case
// Before Handle Error: reverse apply use case
// Handle Error: db connect fail
// After Decorator B1: reverse apply use case
// After Decorator A1: reverse apply use case
```
https://go.dev/play/p/I9cK-VvKjzi
---
Decorator:adds additional responsibilities
強調行為的擴充
Composite:object aggregation
強調資料型別的擴充
增加 樹狀圖 的 子葉
http://hjt-yuki.blogspot.com/2013/11/posd6-composite-pattern.html
---
装饰器模式是透明的,即客户端代码可以处理被装饰的对象和装饰器对象,而不需要了解具体的装饰器。这允许动态地组合装饰器以实现所需的功能。
组合通常是半透明的,因为客户端代码通常需要特定的方式来访问组合对象的子对象。这在构建复杂的树状结构时是有用的。
```go
// 组件接口
// 装饰器模式的主要目的是动态地为对象添加行为,而不需要修改其原始类。
type Component interface {
Operation() string
}
// 装饰器基类
type Decorator struct {
component Component
}
func (d *Decorator) Operation() string {
return d.component.Operation()
}
//
// 组合对象
// 组合是一种组织对象的方式,它的主要目的是将多个对象组合在一起以创建一个更大的对象,
// 或者将对象嵌套在另一个对象中,以实现复杂的层次结构。组合关注的是对象的整体结构。
type Composite struct {
components []Component
}
func (c *Composite) Operation() string {
result := "Composite: ["
for i, component := range c.components {
if i > 0 {
result += ", "
}
result += component.Operation()
}
result += "]"
return result
}
```
[gpt Decorate vs Composition](https://chat.openai.com/share/8529c867-5588-4c67-80cc-28f328fb5216)
### ddd 中的 repository 應該是哪一種 Adapter Pattern, Bridge Pattern ?
先從 Form (結構)來看的話, repository 的 form 和 adapter pattern 的 form 一樣,但和 bridge pattern 的 form 不同。
https://discord.com/channels/937992003415838761/1170343159310598234/1170345219192000584
---
適配器模式(Adapter Pattern)的主要目的是將一個接口轉換成另一個接口,以使不兼容的類別可以一起工作。在適配器模式中,"form" 和 "problem" 可以理解如下:
1. Form(形式部分):
- "Form" 代表適配器的目標接口,也就是客戶端代碼期望與之互動的接口。這個接口定義了客戶端應該使用的方法和行為。
2. Problem(問題部分):
- "Problem" 代表需要被適配的原始接口,也就是現有的類別或系統,其接口不符合客戶端的期望。這部分的接口和行為可能與客戶端要求的接口不相符,因此需要適配以使其能夠與客戶端協同工作。
適配器模式的主要目標是解決不同接口之間的兼容性問題,使原始接口(問題部分)能夠適應客戶端代碼所期望的接口(形式部分)。通過引入適配器,我們可以使客戶端代碼與原始接口進行通信,而不需要對客戶端進行修改或修改原始接口的代碼。
總結來說,"Form" 代表目標接口,客戶端期望與之互動,而 "Problem" 代表原始接口,需要適配以滿足客戶端的需求。適配器模式的目的是使這兩個不兼容的接口能夠協同工作。
---
橋接模式(Bridge Pattern)的主要目的是將抽象部分和實現部分分離,以使它們可以獨立變化。在這種模式中,"抽象部分" 是指一個抽象類別或接口,而"實現部分" 是指具體的實現或實現類別。這個分離有助於**處理多維度的變化**,使系統更具靈活性和可擴展性。
在橋接模式中,"form" 和 "problem" 可以理解如下:
1. Form(形式部分):
- "Form" 代表抽象部分,它是一個描述所需功能的抽象類別或接口。這個部分定義了一個接口,用於表示特定功能的不同變化。它通常包含了用於聲明所需操作的方法。
2. Problem(實現部分):
- "Problem" 代表實現部分,它是一個或多個具體的實現或實現類別。這些類別實際實現了抽象部分所聲明的操作。它們可以具有不同的實現,以適應不同的需求或變化。
橋接模式的核心思想是將抽象部分和實現部分解耦,使它們能夠獨立變化,而不互相影響。這有助於應對多個維度的變化,例如,當你需要擴展抽象部分或實現部分時,你可以輕鬆地添加新的子類別而不會對現有的代碼造成影響。
總結來說,"Form" 表示抽象部分,"Problem" 表示實現部分,橋接模式的目標是在這兩者之間建立一個靈活的關係,以應對系統中的多維度變化。
### Adapter Pattern 和 Bridge Pattern
適配器模式(Adapter Pattern)和橋接模式(Bridge Pattern)確實有一些相似之處,因為它們都涉及到將一個接口轉換為另一個接口,但它們解決的問題和設計方式是不同的。
模式彼此之間的共通點,就是提供某種抽換性,所以如果**用抽換性來去看待**他們的話會感覺彼此幾乎沒啥差別
用 force (約束) 來定義,就會簡單許多
以下是對這兩種模式的詳細說明以及它們解決的問題:
1. 適配器模式(Adapter Pattern):
- 問題:當你有一個現有的類別(或接口)並且其接口與你所需的接口不相容時,需要將它們協同工作,而不需要修改現有的類別。
- 解決方案:適配器模式提供了一個中間層,即適配器,該適配器實現了你需要的目標接口,同時持有一個對現有類別(或接口)的參考。這樣,適配器允許你使用現有類別(或接口),並將其接口轉換為所需的接口。
2. 橋接模式(Bridge Pattern):
- 問題:當你需要分離一個抽象概念(例如抽象類)和其實現,以便它們可以獨立變化時。
- 解決方案:橋接模式將抽象部分(抽象類或接口)和實現部分分離開來。它使用兩層繼承,其中一個層級代表抽象部分,另一個層級代表實現部分。這樣,你可以獨立地修改和擴展抽象部分和實現部分,而不會影響對方。
總結來說,適配器模式用於解決現有類別(或接口)的接口不相容的情況,通過引入一個適配器來實珏你需要的接口。橋接模式用於將抽象部分和實現部分分離,以實現兩者的獨立變化。儘管它們都涉及接口轉換,但它們的設計目標和應用情境是不同的。
### Observer 跟 Iterator
都是 漸進式(progressive) 的取得資料,差別只在
於 Observer 是生產者(Producer)推送資料(push),
而 Iterator 是消費者(Consumer)要求資料(pull)!
![](https://i.imgur.com/fQsxKCn.png)
![](https://i.imgur.com/8pkihNC.png)
Iterator 是一個物件,它的就像是一個指針(pointer),指向一個資料結構並產生一個序列(sequence),這個序列會有資料結構中的所有元素(element)。
```
class Producer {
constructor() {
this.listeners = [];
}
}
egghead.addListener(listener1); // 註冊監聽
egghead.addListener(listener2);
var arr = [1, 2, 3];
var iterator = arr[Symbol.iterator]();
iterator.next();
// { value: 1, done: false }
iterator.next();
```
https://ithelp.ithome.com.tw/articles/10186832
### proxy和decorator
Proxy模式不能動態地添加或分離性質,它也不是爲遞歸組合而設計的。
它的目的是,當直接訪問一個實體不方便或者不符合需要時,爲這個實體提供一個替代者,例如,實體在遠程設備上,訪問受到限制或者實體是持久存儲的。
Proxy模式訪問模型如下:
Client——>Proxy——>ConcreteSubject,目標在ConcreteSubject,如果Client能直接訪問到ConcreteSubject,是不會去麻煩Proxy的。
Decorator模式訪問模型如下:
Client——>ConcreteDecorator——>ConcreteComponent,此時的目標不是ConcreteComponent了,就是ConcreteDecorator,因爲ConcreteDecorator才能提供Client需要的完整功能。
### 策略模式 VS 状态模式
策略模式封装的是不同的算法,算法之间没有交互,以达到算法可以自由切换的目的;而状态模式封装的是不同的状态,以达到状态切换行为随之发生改变的目的。
https://www.kancloud.cn/sstd521/design/193631
### 策略模式 VS 桥梁模式
简单来说,策略模式是使用继承和多态建立一套可以自由切换算法的模式,桥梁模式是在不破坏封装的前提下解决抽象和实现都可以独立扩展的模式。桥梁模式必然有两个“桥墩”——抽象化角色和实现化角色,只要桥墩搭建好,桥就有了,而策略模式只有一个抽象角色,可以没有实现,也可以有很多实现。
https://www.kancloud.cn/sstd521/design/193634
### 工廠方法 v.s. 抽象工廠
工廠方法: 針對一個產品等級
抽象工廠: 針對多個產品等級
抽象工廠:
介面規定工廠現在生產的不是一種產品, 而是生產一系列所有的裝備
## 模式分析
不管是什麼模式 當然至少都會有class之間的collaboration
所以重點是 模式的
1. "動機"是什麼?
2. 想封裝的是什麼?
舉個例子 挑Strategy pattern和Adapter pattern來看
Strategy pattern封裝了一個演算法或者一個行為
而Adapter模式封裝了另一個不相容的介面
若談論這兩個模式class 之間的 Collaboration
兩個模式當然都必須透過委派來做到
但這並不是分類的重點
重點是 以動機的角度來看
Strategy pattern注重將'責任'劃分出去的行為委派
Adapter pattern注重的是相容的'介面架構'
https://www.facebook.com/groups/1403852566495675/2499568190257435/?comment_id=2501764070037847¬if_id=1577012294474077¬if_t=group_comment&ref=notif
## 组合作为基础抽象模式
当你将组合作为基础抽象模式后,自然就会发现最小粒度的组合元素自然就是函数,而一旦语言支持将函数作为变量和返回值来进行传递,那么很多复杂的设计模式就变成了基础功能了。
## 與對象相關的責任
1. 如何建造該對象
2. 對象本身有什麼功能
3. 使用者(actor, client) 如何使用該對象
設計模式的藝術, p57
藉由引入 工廠或創建類的函數或物件
使用者不會涉及如何建造該對象
工廠函數也不會涉及對象如何使用
## 根據情境 bound context 決定關係
人開車, 人 和 車 是什麼關係?
這答案沒有絕對性
會**依據你所在的領域跟情境**, 決定他們之間的關係
如果是賽車場, 介紹一位車手
那人和車是 association 關係
如果是日常生活, 介紹一個人
那人和車是 dependency 關係
## 委託
在委託中,呼叫者物件必須將自己傳給被呼叫者物件,方能讓被呼叫者存取到呼叫者。
https://blog.csdn.net/fengzhongzhishenfu/article/details/23592863
## Top-down 和 Bottom-up 設計方法
Alexander說,套用pattern形成pattern language的精神是一種「整體先於部分,然後透過差異化的過程將整體逐步展開」。
Alexander的Pattern/Pattern Language設計方法是一種由上而下的方法,而GoF Design Pattern是一種由下而上的方法。
Alexander認為,如果要完成一個「整體性的設計」,無法經由「由下而上」的組合過程來達到。因為如果沒有先具備「整體」的概念,則「由下而上」的過程最後組裝出來的產品或是設計很可能會「長歪掉」
GoF Design Pattern則沒有那麼遠大的目標,主要的用途就是解決比較小的設計問題。至於採用「由下而上」的方式套用了一堆Design Pattern之後所形成的「整體」長得如何,是否「完整」,則不是Design Pattern所能或所想要解決的問題。
https://teddy-chen-tw.blogspot.com/2014/03/top-downbottom-up.html
## Law of Demeter: Principle of Least Knowledge
迪米特法則並不是要開發者,看到每行程式碼就開始數著出現幾個dot呼叫
真正意義在於多個dot出現時,暗示著開發者可能正在透過這些dot破壞封裝(Encapsulation)
對迪米特法則經常可見的一個簡單比喻是「別跟陌生人聊天」,然而,真正的意義應該是「別探朋友隱私」
最重要的是,希望開發者能進一步思考,在這個呼叫過程中是否破壞了封裝,以便及時採取手段,來阻止相依性進一步地蔓延,不揭露自身隱私,不探他人隱私,相依性的降低,就只是必然的結果!
https://www.ithome.com.tw/voice/98670
## 關注點分離 separation of concerns (SoC)
从软件设计上看,我们都是在实践找关注点是什么,分层只是实践关注点的一种。
往上抽象,就是4个字:单一职责。
可以纵向和横向两个角度出发:纵向是具有依赖的;横向(Cross-cutting concern)是水平可替换的。
分层就是纵向考虑的,每个层的职责不一样,但层与层之间有相互关联;接口实现是横向的,接口的实现是可替换的。
关注点其实很有意思,它看待的角度是不一样的,拿面向接口来说,接口本身的关注点是能做什么,接口的实现关注点是如何做的问题。
## 單一職責
當從一個函數或類別中, 提取共用程式碼時
必須意識到此類別違反了單一職責, 或許可以將提取的程式碼
移至另一個類別, 甚至進一步抽象化
實作上最困難的地方
原則很簡單,但實作上為何困難?
最難的就是定義所謂的『職責』,以及切割class的粒度取捨。
https://dotblogs.com.tw/hatelove/archive/2010/10/16/single-responsibility-principle.aspx
https://ithelp.ithome.com.tw/articles/10107516
## 如何定義職責
這邊要找出「誰,做什麼事」,有一個相當相當簡單的技巧,相信大家一學就會。針對前面透過人話所整理出來的function,只要找出該function代表的意義中的「主詞」、「動詞」、「受詞」即可。
什麼意思?很簡單:
主詞:代表類別
動詞:代表方法
受詞:通常是方法參數
形容詞:通常是呼叫物件行為後,物件產生的狀態變化
https://ithelp.ithome.com.tw/articles/10105596
## 依赖的三种写法
1. 建構的對象時候, 就注入依賴的對象
2. 定義 set method, 先建構對象, 之後呼叫 set() 注入依賴
3. 介面方法宣告依賴對象, 先建構對象, 對象使用相關行為時, 同時注入依賴
https://www.kancloud.cn/sstd521/design/193500
## DTO 的 應用場景
"資料傳遞物件"。沒錯,它只是一個帶有資料的物件,沒有任何的操作行為
- Data Transfer Object亦稱為Value Object,因此物件僅包含屬性值而無任何操作行為(Behavior)
- Service Layer(SL)為Presentation Layer(PL)與Business Layer(BL)的中介層,用於降低PL與BL的耦合性,並將實際的商務邏輯功能委派給BL執行,一個SL操作(coarse-grained operation)通常會包含一個以上的BL操作(fine-grained operation)
https://www.petekcchen.com/2010/12/how-to-use-data-transfer-object.html?m=1
## 內聚力 (Cohesion)
用來評估一個模組內部所處理事情的相關程度
* 功能內聚力 (Functional Cohesion)
* 順序內聚力 (Sequential Cohesion)
* 溝通內聚力 (Communication Cohesion)
* 暫時內聚力 (Temporal Cohesion)
* 程序內聚力 (Procedural Cohesion)
* 邏輯內聚力 (Logical Cohesion)
* 偶發內聚力 (Coincidental Cohesion)
https://hackmd.io/@k139/r1y-9LmK4/%2Fs%2FH1x46U7KN?type=book#%E7%B5%90%E6%A7%8B%E5%8C%96%E5%88%86%E6%9E%90%E8%88%87%E8%A8%AD%E8%A8%88%E8%A9%95%E4%BC%B0%E6%BA%96%E5%89%87
## 耦合力 (Coupling)
一種衡量模組間相互關聯強度的方法
當解決了一模組內的錯誤狀況,但對其他的模組引起了新的錯誤,這種現象稱為連鎖反應 (Ripple Effect)。
資料耦合力 (Data Coupling)
資料結構耦合力 (Stamp Coupling)
控制耦合力 (Control Coupling)
共同耦合力 (Common Coupling)
內容耦合力 (Content Coupling)
https://hackmd.io/@k139/r1y-9LmK4/%2Fs%2FH1x46U7KN?type=book#%E7%B5%90%E6%A7%8B%E5%8C%96%E5%88%86%E6%9E%90%E8%88%87%E8%A8%AD%E8%A8%88%E8%A9%95%E4%BC%B0%E6%BA%96%E5%89%87
## 內聚性原則 與 耦合性原則
除了「物件導向的五大設計原則」以外,還有「元件的內聚性原則」與「元件的耦合性原則」。
實際上的順序,應該是要: **內聚性先,耦合性後**
- 耦合性三大原則
- ADP 無環依賴原則
在元件的依賴關係圖中不允許出現環
- SDP 穩定依賴原則
朝著穩定方向進行依賴,就是由不穩定的元件,朝向穩定的元件,建立依賴關係
- SAP 穩定抽象原則
元件的抽象程度應該與元件的穩定程度一致
「抽象」: 則代表的是 只表達「概念」但並不「實作」,也就是越重要的元件,應該越只表達純粹的「概念」
抽象的元件非常穩定(畢竟沒有任何實作)
因此是不穩定的元件依賴的理想目標
https://youtu.be/cSQY0rA7FPU
https://gamma-ray-studio.blogspot.com/2020/12/3.html
- 內聚性三大原則
- (release) REP 再使用性 - 發佈等價原則
再使用性的細緻度就是發佈的細緻度, 標出版本號
(我不 release 就沒有 reuse 啦, 確保相容性, 尤其是多人合作容易各改各的, 分別 release)
- (change) CCP 共同封閉原則
將那些會因著相同理由、再相同時間發生變化的類別,收集到相同的元件之中。
將那些在不同時間、因著不同理由發生變化的類別,分割到不同的元件之中。
(好維護, 所以改變只由一個元件變化, 減少 release 次數)
- (use) CRP 共同重複使用原則
不要強迫元件的使用者依賴他們不需要使用的東西
(不會因為非核心的功能改變, 而重新 release)
REP和CCP是包容性原則(Inclusive) 這兩個傾向於讓元件變更大
CRP是一個排除性原則(Exclusive) 這個原則傾向讓元件變更小
![](https://i.imgur.com/Pi9eXsr.png)
edge 的意思是:
放棄某個原則, 會有什麼後果
內聚性原則,加入了第四維度「時間」,想要強調的則是「動態」
在專案開發初期:
CCP 原則的重要性會大於 REP原則(可發展比重複使用更重要)
所以這時,第一個應該要思考的是
如何恰當的「分類」,而不是思考未來該如何「發布版本」。
一開始小公司人少, 可以先不重視 release 是否一起發布
發布版本號管理比較不重要
後面公司大了, 人多以後
要重視 release 後, 套件是否可以被正確一起使用
此處的 reuse 不是指程式的複用, 而是對客戶端來說, 此套件是否可以穩定複用
專案後期才會開發出, 想要讓其他專案複用的套件
此時版本號就很重要
為什麼這裡的Cohesion這麼複雜呢 之前講內聚性的時候不就是越高越好嗎?
之前我們講的是一個類別或是一個函式 規模比較小 在這裡我們講的是元件內聚性
元件是什麼? 元件是可以被部署(deploy)的最小單位(在Java裡面是jar檔 Ruby裡面是gem檔)
既然我們討論的規模變大了 那就不能只是單純的Cohesion越高越好了 還得考慮reuse的難易程度 release的次數等等
https://youtu.be/wRyzCktcbNQ
https://gamma-ray-studio.blogspot.com/2020/12/repccpcrp_1.html
## 如何解開循環依賴
要是今天來了一個環 Entities指到Authorizer
產生循環依賴
![](https://i.imgur.com/70lmpEX.png)
解法一: DIP
![](https://i.imgur.com/zxdCLvO.png)
解法二: 新開一個Entity跟Authorizer都共同依賴的元件
![](https://i.imgur.com/TTl3szi.png)
https://www.jyt0532.com/2020/03/27/coupling/
## 穩定元件 不穩定元件
穩定度 ?
也是以「工作量」的多寡來決定
穩定的元件
元件被很多的功能依賴,但本身沒有去依賴任何東西
改了它,就代表了巨大的「工作量」
所以這個元件是「穩定」的
![](https://i.imgur.com/d17ZAel.png)
不穩定的元件
如果這個元件,沒有功能依賴它,但它依賴了很多外部的元件
修改它的「工作量」非常的低
所以這個元件是「不穩定」的
![](https://i.imgur.com/Zj0OSyf.png)
理想的狀況,就是由不穩定的元件,朝向穩定的元件,建立依賴關係
「穩定」與「不穩定」並不是一個「好」與「壞」的關係
而是這個「穩定度」與元件的「特性」是否是相符合。
https://gamma-ray-studio.blogspot.com/2020/12/3.html
假設今天你是負責元件Flexible的工程師
這是個很容易被改動 你也預期會需要一直改動的元件
可是今天負責Stable的團隊 使用了你寫的一個類別
因為別人依賴你 害你不能隨心所欲的改動 那你該怎麼辦呢?
![](https://i.imgur.com/rvHx3cL.png)
應該由不穩定的元件,朝向穩定的元件,建立依賴關係
我們使用 DIP 來解決這問題
![](https://i.imgur.com/xfs082g.png)
https://www.jyt0532.com/2020/03/27/coupling/
## IOC(控制反轉) , DI(依賴注入)
Inversion of Control 控制反轉
控制反轉是一個設計思想 ,把對於某個物件的控制權移轉給第三方容器
https://dotblogs.com.tw/daniel/2018/01/17/140435
Dependency inversion: good, dependency injection: bad, but loose source level coupling? Priceless
https://twitter.com/davecheney/status/871939730761547776
DI Container做的事情就是:
* 幫助更簡單組織複雜的物件。
* 幫助你控制複雜物件的生命週期。
* 幫助你針對抽象化去Decorate不同的責任(Intercept)。
Dependency Injection三大概念的產生:
* Object Composition
* Object Lifetime Management
* Object Interception(Cross-Cutting-Concern)
而這些事情都只會發生在Composition Root(Entry Point)。
控制物件的生命週期, 廢除 單例模式
https://medium.com/@zxuanhong/dependency-injection%E6%98%AF%E4%BB%80%E9%BA%BC-ae83f7f87d6d
## 不同 layer 應該關注的焦點
切換到防腐層
要以 外部為核心模型 (穩定元件)
不要以 domain
不然重複性很高
repoImp變成類似資料存取的應用層
## Cohesion vs Coupling
Cohesion: degree to which the various parts of a software component are related
Coupling: level of inter dependency between various software component
Cohesion指的是 一個元件裡面的不同元件的關聯性
Coupling指的是 不同元件裡面的依賴關係強度
https://www.jyt0532.com/2020/03/23/isp/
## 時空耦合(temporal coupling)
二個動作只因為同時間發生,就被包裝在一個模組中。
ddd context mapping 發生的地方
## 避免隱藏時序耦合
令每個函數回傳下一個函數所需要的 input, 藉此建立時序的耦合
建立生產線能夠暴露時序耦合性
## 順序的耦合性
temporal coupling 時空耦合
时间是软件架构中常常被忽视。
时间有两个方面很重要:
* 并发(事情在同一时间发生)
* 次序(事情在时间中的相对位置)
时间耦合的例子如下:
* 方法 A 总是在方法 B 之前调用
* 同时只能运行一个报告
* 在接收到按钮点击前,你必须等待屏幕重画
* 我们有必要在工作流分析,架构,设计,部署等开发领域中减少任何基于时间的依赖
https://atprogrammer.com/2019/04/18/%E6%97%B6%E9%97%B4%E8%80%A6%E5%90%88-Temporal-Coupling/
若想降低時間面的共生性: 多個元件需要知道正確的執行順序
可以將 rpc 風格 的程式碼換成事件
事件是一種 名稱面的共生性: 多個元件只需要使用同一個事件名稱
py 架構, p170