ksCaesar
    • Create new note
    • Create a note from template
      • Sharing URL Link copied
      • /edit
      • View mode
        • Edit mode
        • View mode
        • Book mode
        • Slide mode
        Edit mode View mode Book mode Slide mode
      • Customize slides
      • Note Permission
      • Read
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Write
        • Only me
        • Signed-in users
        • Everyone
        Only me Signed-in users Everyone
      • Engagement control Commenting, Suggest edit, Emoji Reply
      • Invitee
    • Publish Note

      Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

      Your note will be visible on your profile and discoverable by anyone.
      Your note is now live.
      This note is visible on your profile and discoverable online.
      Everyone on the web can find and read all notes of this public team.
      See published notes
      Unpublish note
      Please check the box to agree to the Community Guidelines.
      View profile
    • Commenting
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
      • Everyone
    • Suggest edit
      Permission
      Disabled Forbidden Owners Signed-in users Everyone
    • Enable
    • Permission
      • Forbidden
      • Owners
      • Signed-in users
    • Emoji Reply
    • Enable
    • Versions and GitHub Sync
    • Note settings
    • Engagement control
    • Transfer ownership
    • Delete this note
    • Save as template
    • Insert from template
    • Import from
      • Dropbox
      • Google Drive
      • Gist
      • Clipboard
    • Export to
      • Dropbox
      • Google Drive
      • Gist
    • Download
      • Markdown
      • HTML
      • Raw HTML
Menu Note settings Sharing URL Create Help
Create Create new note Create a note from template
Menu
Options
Versions and GitHub Sync Engagement control Transfer ownership Delete this note
Import from
Dropbox Google Drive Gist Clipboard
Export to
Dropbox Google Drive Gist
Download
Markdown HTML Raw HTML
Back
Sharing URL Link copied
/edit
View mode
  • Edit mode
  • View mode
  • Book mode
  • Slide mode
Edit mode View mode Book mode Slide mode
Customize slides
Note Permission
Read
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Write
Only me
  • Only me
  • Signed-in users
  • Everyone
Only me Signed-in users Everyone
Engagement control Commenting, Suggest edit, Emoji Reply
Invitee
Publish Note

Share your work with the world Congratulations! 🎉 Your note is out in the world Publish Note

Your note will be visible on your profile and discoverable by anyone.
Your note is now live.
This note is visible on your profile and discoverable online.
Everyone on the web can find and read all notes of this public team.
See published notes
Unpublish note
Please check the box to agree to the Community Guidelines.
View profile
Engagement control
Commenting
Permission
Disabled Forbidden Owners Signed-in users Everyone
Enable
Permission
  • Forbidden
  • Owners
  • Signed-in users
  • Everyone
Suggest edit
Permission
Disabled Forbidden Owners Signed-in users Everyone
Enable
Permission
  • Forbidden
  • Owners
  • Signed-in users
Emoji Reply
Enable
Import from Dropbox Google Drive Gist Clipboard
   owned this note    owned this note      
Published Linked with GitHub
Subscribed
  • Any changes
    Be notified of any changes
  • Mention me
    Be notified of mention me
  • Unsubscribe
Subscribe
## function vs method function: - 易變邏輯(未來擴充多) - 獨立於物件之外的「水平跨模組或底層功能」的基礎單元 - 功能的邏輯並不依賴、也不修改物件狀態,或者操作是通用的工具行為 method: - 穩定邏輯(固定規則) - 是附屬於物件或類別的「垂直層級化行為」 --- * **如果 HandType 未來可能不斷擴充、變動** → 用外部的 **function / static 方法**(像 `HandType.from(hand)`)比較好。 * 好處:新增或調整 HandType 時,不用動 `Hand` 類別本身; * 壞處:你需要額外的一個「判斷者/工廠」來判斷 hand 的類型。 * **如果 HandType 就是固定的一套,跟 Hand 的行為緊密綁在一起** → 放在 **`Hand` 類別本身**(`hand.getHandType()`)比較好。 * 好處:物件導向語義直觀; * 壞處:變動時要改 `Hand` 類別,耦合度高。 --- | 面向 | 用外部 `function`/`static` 方法 (`HandType.from(hand)`) | 用 `Hand` 自己的方法 (`hand.getHandType()`) | | --------------- | -------------------------------------------------- | ------------------------------------- | | **擴充性** | 高:新增或改變 `HandType` 規則時,不用動 `Hand` 類別 | 低:每次規則改變都得改 `Hand` 類別 | | **耦合度** | 低:`Hand` 只當資料容器,不知道判斷邏輯 | 高:`Hand` 同時包含資料和判斷邏輯 | | **物件導向語義** | 偏「工具式」或「服務式」設計,`Hand` 是被動的 | 偏「物件自我負責」,`Hand` 是主動的 | | **測試性** | 容易測試:純函數,輸入/輸出明確 | 測試需建立 `Hand` 物件才能測 | | **靈活性(不同遊戲規則)** | 容易:可以做多個不同的判斷器/工廠 | 困難:不同規則都要塞進 `Hand` | | **維護成本** | 稍高:需要額外一個類別/方法維護 | 稍低:所有邏輯在 `Hand` 內,不需額外類別 | | **語法直覺** | `HandType.from(hand)`(像「裁判」在外面判斷) | `hand.getHandType()`(像「手牌自己知道自己是什麼」) | --- **總結建議** * **如果 HandType 邏輯是易變、可插拔、不同上下文會不同** → 用外部 `function` / `static` 方法比較合適。 * **如果 HandType 是固定的、就是 Hand 的本質屬性** → 放在 `Hand` 本身比較合理。 要不要我順便幫你示範「如何在同一套程式裡同時支援多種 HandType 判斷規則(策略模式)」?(這個做法可以兼顧擴充性 + 物件導向語義) https://chatgpt.com/share/68cb4b87-53c4-800d-9ed3-09be54798fa5 ## 設計軟體的三大面向 UML 用來幫助開發者理解和設計軟體的三大面向,每個面向有其對應的圖形: - **靜態(結構) - 類別圖** 類別圖描述了系統的靜態結構,包括物件的屬性、方法以及類別之間的繼承和關聯。這有助於理解系統的整體架構,特別是對象導向開發中,保證系統架構合理且具備擴展性。 - **動態(行為) - 循序圖** 循序圖顯示了系統中對象之間如何隨著時間交互。它展示了訊息流動和時間順序,能幫助理解系統內部行為的執行流程。 - **狀態(綁定特定屬性的行為) - 狀態機圖** 狀態機圖用來描述物件如何根據事件變更狀態,並規定不同狀態間的轉換。這對於處理複雜的狀態邏輯尤為重要,尤其是需要管理多個狀態轉換的系統。 https://www.facebook.com/100093723731513/posts/460111237122997/?rdid=hl5A0qlqU4N9XCBC# ## Composition Root [依賴注入:原理、實作與設計模式 (Dependency Injection: Principles, Practices, Patterns, 2/e)](https://www.tenlong.com.tw/products/9789864344987?list_name=i-rd) 組合根(Composition Root)是指應用程式中負責組織所有依賴關係的單一位置。這個概念在 依賴注入(Dependency Injection, DI) 設計模式中非常重要,因為它確保所有的相依物件(dependencies)都是在應用程式的啟動階段被組合起來,而不是在應用程式的執行過程中隨機產生。 組合根的運作方式: 1. 集中管理依賴關係:所有的相依物件應該在一個地方初始化,而不是分散在程式的各個部分。 1. 使用 DI 容器(可選):在組合根中,可以手動建立相依物件或使用 DI 容器(例如 Python 的 Dependency Injector) 來管理依賴關係。 1. 確保依賴的單一來源:所有的物件都是從組合根開始組裝,這樣可以避免不必要的耦合並提高可測試性。 --- **DI 反模式 (Dependency Injection Anti-Patterns)** **5.1 控制狂 (Control Freak)** 定義:物件在內部建立自身的依賴,而不是透過組合根提供,導致高耦合與低測試性。 問題點: - 依賴關係無法輕易替換或 mock,影響測試與擴展性。 - 物件內部決定如何建立依賴,使得變更影響範圍擴大。 解決方案: 應透過建構子或方法注入所需的依賴,並在組合根管理依賴的建立與組裝。 --- **5.2 服務定位 (Service Locator)** 定義:物件透過一個全域的服務定位器來獲取依賴,而不是透過建構子或方法注入。 問題點: - 依賴關係變得隱式,難以追蹤與維護。 - 測試時無法輕易替換依賴,影響單元測試的可靠性。 - 服務定位器本身成為全域狀態,可能導致意外的副作用。 解決方案: 應直接透過建構子或方法注入所需的依賴,確保依賴關係的明確性與可測試性。 --- **5.3 環境物件 (Ambient Context)** 定義:依賴透過全域物件 (如單例) 提供,而不是透過建構子或方法注入,導致環境依賴過強。 問題點: - 測試時無法控制環境物件的行為,使得測試結果不穩定。 - 依賴關係變得隱式,影響可讀性與維護性。 - 在不同環境或執行緒中,可能出現預期外的共享狀態問題。 解決方案: 應透過依賴注入提供必要的環境資訊,例如時間提供者或設定物件,而非直接存取全域變數。 --- **5.4 限制性建構 (Restrictive Instantiation)** 定義:過度使用工廠模式、靜態方法或其他強制建立方式,導致測試與擴展受限。 問題點: - 物件的建立方式受到限制,導致依賴無法輕易替換或 mock。 - 影響可測試性,無法在測試環境中使用不同的依賴實作。 - 破壞開放封閉原則 (OCP),需要修改現有程式碼才能適應新需求。 解決方案: 應避免強制的工廠方法,讓物件透過建構子接收依賴,使其更靈活且易測試。 --- 這些反模式在某些情境下可能有其合理性,但大多數時候會降低系統的可維護性與測試性。因此,應優先考慮透過 **組合根** 來管理依賴關係,並避免過度依賴這些不良模式。 ## 如何分辨設計模式 您要花多久,才能分出 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建議找到問題域中存在的各種概念(共性)和具體的實現(可變性)。 接下來我們最感興趣的是找到其中的概念,但是這個過程中也發現了許多可變性。 將繼承看成一種將變化概念化的方法,而不是創建現有物件的特殊情況。 ## 關聯類別(association class) 關聯類別(assocation class)允許你在關聯上面增加屬性、操作和其它(類別)特性。 有些說法是從 E-R(Entity-Relationship)的角度來說明 association class 是為了保存及紀錄額外的資訊(additional information)而衍生。因為在多對多的情況下,資訊儲存在任一個 class 下均不甚理想 記錄關於學生選擇了哪些課程的訊息,該記錄儲存在 "學生" 或是 "科目" 類別內均不太適合。所以為了記錄關於選課的資訊,於是衍生出關聯類別 "Student-Course" 或稱之為 "選課" ![image](https://hackmd.io/_uploads/HysovYAQyx.png) 文章作者認為,稱之為 "關聯類別(association class)" 實在挺不自然的,怎麼解釋說明都感覺不是那麼的合理化。 若從 "交易" 的角度來切入,關聯類別消失了,取而代之的是 "事件(Event)" "選課" 這個事件的發起者為 "學生",而"學生" 所選課的 "物" 即為 "課程" 交易結束後, 產生 "選課紀錄" 事件 https://www.kenming.idv.tw/uml_2_0_e_e_mei_a_y_association_class/ https://online.visual-paradigm.com/tw/diagrams/templates/class-diagram/uml-class-diagram-association-class-and-self-association/ ## uml ![image](https://hackmd.io/_uploads/BJnZZ2Q_a.png) ![image](https://hackmd.io/_uploads/S1bIzhmu6.png) ### 1. **組合(Composition)** - **強關聯**:表示強依賴關係,部分(Part)無法獨立於整體(Whole)存在。 - **生命週期依附**:當整體被銷毀時,部分也隨之被銷毀。 - **符號**:實心菱形連接到「整體」的一端。 - **範例**: - 人和心臟的關係:心臟無法脫離人體而存在。 ### 2. **聚合(Aggregation)** - **弱關聯**:表示弱依賴關係,部分可以獨立於整體存在。 - **生命週期獨立**:即使整體被銷毀,部分仍然可以存在。 - **符號**:空心菱形連接到「整體」的一端。 - **範例**: - 班級和學生的關係:學生可以存在於不同班級或無班級的情況。 ## 责任链模式 如何設計 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 和 Bridge **Visitor 模式** - 在現有結構(元素)上動態添加新功能(訪問者)。 - 使用 `accept` 方法讓訪問者訪問每個元素,新增操作邏輯時無需修改結構本身。 **Bridge 模式** - 抽象層與實現層解耦,使它們可以獨立發展。 - 通過**組合**將抽象類(Abstraction)與實現類(Implementation)分開,實現多維度擴展。 | **特性** | **Visitor 模式** | **Bridge 模式** | | -------------- |:------------------------------------------------------------:| ------------------------------------------ | | **分離的目標** | 將行為與結構分離 | 將抽象與實現分離 | | **擴展方向** | 便於新增行為,不影響元素結構 | 便於抽象與實現的獨立擴展 | | **實現方式** | 透過訪問者動態新增功能 | 使用組合模式,將抽象與實現通過關聯解耦 | | **適合的場景** | **頻繁添加行為**而不修改對象結構時,例如圖形的面積與周長計算 | 抽象和實現多維度組合,例如形狀與顏色的搭配 | ## 訪問者模式 (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 橋接模式主要用於**將抽象部分與其實現部分分離**,使它們可以獨立變化。這在解決以下問題時非常有效: 1. 抽象和實現之間的多維度變化(如不同設備上的操作系統與硬體的組合)。 2. 減少類別層次結構中的複雜性,避免因為多維度的組合而導致的類別爆炸。 如果你的組合爆炸源於**行為與實體屬性之間的耦合**,橋接模式可以有效解耦。例如: - **抽象層**:定義怪物的行為(如 `action()` 方法)。 - **實現層**:不同的行為具體實現(如「走」、「跑」、「跳」等)。 | **特性** | **橋接模式** | **裝飾者模式** | | ------------ | -------------------------------------------------- | -------------------------------------------- | | **適用場景** | 當行為和實體有多維度的變化(抽象與實現分離)。 | 當需要動態添加、組合或擴展功能(行為組合)。 | | **靈活性** | 靜態組合,結構化的方式來處理維度。 | 動態組合,能靈活增減行為。 | | **維護性** | 減少抽象和實現層之間的耦合,結構清晰。 | 可用於大幅降低行為組合的複雜度。 | | **典型用途** | 操作系統與硬體、繪圖 API(如 OpenGL vs DirectX)。 | UI 元素的樣式、怪物行為的擴展。 | | **疊加次數** | 只能疊加 1 次 | 可以疊加 N 次 | 橋接模式處理的是多維度耦合的問題,而非行為的靈活組合。如果你的重點是: 1. 行為的動態擴展或組合(如怪物隨機擁有「走」、「跑」、「跳」的能力),則**裝飾者模式更適合**。 2. 但如果怪物的行為是靜態的、固定的維度(如「地面怪物」和「空中怪物」有不同的移動方式),**橋接模式會更合適**。 ![image](https://hackmd.io/_uploads/rJltMOEEyg.png) --- 《設計模式》一書中對橋接模式的解釋是這樣敘述的: 「將抽象與實現解耦,使它們都可以獨立地變化」 然能聽懂這句話的每個字,但卻對整個句子的意思頭緒,這是怎麼回事呢? 因為誤解了實現的含意。這裡的實作是指 使用抽象類別及其衍生類別用來實作自己的物件 (而不是抽象類別的衍生類別,這些衍生類別稱為具體類別) 對 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的思考,這個物件要提供哪些功能給外面用」 ## 動機 和 目的 設計模式通常分為三大類: 1. 創建型模式(Creational Patterns) - 負責物件的建立方式 2. 結構型模式(Structural Patterns) - 處理類別與物件的組合方式 3. 行為型模式(Behavioral Patterns) - 針對物件之間的互動與行為 --- 1. 創建型模式(Creational Patterns) 這類模式專注於如何建立物件,可以讓物件建立過程更有彈性、避免耦合。 - 工廠方法模式(Factory Method):定義一個介面,讓子類決定要建立哪種物件 - 抽象工廠模式(Abstract Factory):提供一組相關或相依物件的建立方法 - 建造者模式(Builder):分離物件的建立與組裝過程,提高靈活性 - 原型模式(Prototype):使用物件的複製來建立新的物件 - 單例模式(Singleton):確保一個類別只有一個實例,並提供全域存取點 Go 的應用: - Go 沒有類別(Class),但可以透過結構體(struct)+ 工廠函式來實作 Factory Pattern。 - Singleton 在 Go 通常用 `sync.Once` 來確保單一實例。 --- 2. 結構型模式(Structural Patterns) 這類模式關注如何組合類別與物件,讓系統更靈活、可擴展。 - 適配器模式(Adapter):將不相容的介面轉換成可用的介面 - 橋接模式(Bridge):將抽象與實作分開,以獨立變更 - 組合模式(Composite):讓單個物件與物件集合能夠統一處理 - 裝飾模式(Decorator):動態擴展物件的功能,而不影響原始類別 - 外觀模式(Facade):對外提供一個統一的介面,簡化系統使用 - 享元模式(Flyweight):共享物件來降低記憶體使用 - 代理模式(Proxy):控制對某物件的存取,增加額外邏輯(如權限控管) --- 3. 行為型模式(Behavioral Patterns) 這類模式關注物件之間的互動方式,確保物件之間的溝通高效、低耦合。 - 責任鏈模式(Chain of Responsibility):讓請求在多個處理者之間傳遞,直到某個處理者處理 - 命令模式(Command):將請求封裝成物件,支援撤銷與記錄 - 迭代器模式(Iterator):提供統一的方式來遍歷集合物件 - 中介者模式(Mediator):透過中介者來降低多個物件的直接耦合 - 備忘錄模式(Memento):儲存物件的狀態,支援回溯 - 觀察者模式(Observer):讓一個物件的變更通知多個對象 - 狀態模式(State):讓物件在不同狀態下改變行為 - 策略模式(Strategy):定義一系列演算法,讓物件可動態替換策略 - 範本方法模式(Template Method):定義演算法骨架,允許子類改變部分邏輯 - 訪問者模式(Visitor):讓一組物件支援額外操作,而不改變原始類別 --- 總結: | 類別 | 目的 | Go 的典型應用 | |------|------|--------------| | 創建型(Creational) | 控制物件的建立方式 | 工廠模式 (`NewXXX()` 函式)、單例模式(`sync.Once`) | | 結構型(Structural) | 管理物件之間的組合方式 | Adapter (`interface`)、Decorator(包裝 `struct`)、Facade(`Service struct`) | | 行為型(Behavioral) | 定義物件之間的互動 | 觀察者(`channel`)、策略模式(函式指標)、責任鏈(`next` 指標) | 你對哪個模式有興趣,想要更深入探討嗎? --- ![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&notif_id=1577012294474077&notif_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

Import from clipboard

Paste your markdown or webpage here...

Advanced permission required

Your current role can only read. Ask the system administrator to acquire write and comment permission.

This team is disabled

Sorry, this team is disabled. You can't edit this note.

This note is locked

Sorry, only owner can edit this note.

Reach the limit

Sorry, you've reached the max length this note can be.
Please reduce the content or divide it to more notes, thank you!

Import from Gist

Import from Snippet

or

Export to Snippet

Are you sure?

Do you really want to delete this note?
All users will lose their connection.

Create a note from template

Create a note from template

Oops...
This template has been removed or transferred.
Upgrade
All
  • All
  • Team
No template.

Create a template

Upgrade

Delete template

Do you really want to delete this template?
Turn this template into a regular note and keep its content, versions, and comments.

This page need refresh

You have an incompatible client version.
Refresh to update.
New version available!
See releases notes here
Refresh to enjoy new features.
Your user state has changed.
Refresh to load new user state.

Sign in

Forgot password

or

By clicking below, you agree to our terms of service.

Sign in via Facebook Sign in via Twitter Sign in via GitHub Sign in via Dropbox Sign in with Wallet
Wallet ( )
Connect another wallet

New to HackMD? Sign up

Help

  • English
  • 中文
  • Français
  • Deutsch
  • 日本語
  • Español
  • Català
  • Ελληνικά
  • Português
  • italiano
  • Türkçe
  • Русский
  • Nederlands
  • hrvatski jezik
  • język polski
  • Українська
  • हिन्दी
  • svenska
  • Esperanto
  • dansk

Documents

Help & Tutorial

How to use Book mode

Slide Example

API Docs

Edit in VSCode

Install browser extension

Contacts

Feedback

Discord

Send us email

Resources

Releases

Pricing

Blog

Policy

Terms

Privacy

Cheatsheet

Syntax Example Reference
# Header Header 基本排版
- Unordered List
  • Unordered List
1. Ordered List
  1. Ordered List
- [ ] Todo List
  • Todo List
> Blockquote
Blockquote
**Bold font** Bold font
*Italics font* Italics font
~~Strikethrough~~ Strikethrough
19^th^ 19th
H~2~O H2O
++Inserted text++ Inserted text
==Marked text== Marked text
[link text](https:// "title") Link
![image alt](https:// "title") Image
`Code` Code 在筆記中貼入程式碼
```javascript
var i = 0;
```
var i = 0;
:smile: :smile: Emoji list
{%youtube youtube_id %} Externals
$L^aT_eX$ LaTeX
:::info
This is a alert area.
:::

This is a alert area.

Versions and GitHub Sync
Get Full History Access

  • Edit version name
  • Delete

revision author avatar     named on  

More Less

Note content is identical to the latest version.
Compare
    Choose a version
    No search result
    Version not found
Sign in to link this note to GitHub
Learn more
This note is not linked with GitHub
 

Feedback

Submission failed, please try again

Thanks for your support.

On a scale of 0-10, how likely is it that you would recommend HackMD to your friends, family or business associates?

Please give us some advice and help us improve HackMD.

 

Thanks for your feedback

Remove version name

Do you want to remove this version name and description?

Transfer ownership

Transfer to
    Warning: is a public team. If you transfer note to this team, everyone on the web can find and read this note.

      Link with GitHub

      Please authorize HackMD on GitHub
      • Please sign in to GitHub and install the HackMD app on your GitHub repo.
      • HackMD links with GitHub through a GitHub App. You can choose which repo to install our App.
      Learn more  Sign in to GitHub

      Push the note to GitHub Push to GitHub Pull a file from GitHub

        Authorize again
       

      Choose which file to push to

      Select repo
      Refresh Authorize more repos
      Select branch
      Select file
      Select branch
      Choose version(s) to push
      • Save a new version and push
      • Choose from existing versions
      Include title and tags
      Available push count

      Pull from GitHub

       
      File from GitHub
      File from HackMD

      GitHub Link Settings

      File linked

      Linked by
      File path
      Last synced branch
      Available push count

      Danger Zone

      Unlink
      You will no longer receive notification when GitHub file changes after unlink.

      Syncing

      Push failed

      Push successfully