談談軟體元件的內聚與耦合(2)

更新 發佈閱讀 11 分鐘

本篇文章將會繼續上一篇的內容,還沒看過上一篇的朋友,記得先看完上一篇再繼續本篇的閱讀唷。

連結﹔談談軟體元件的內聚與耦合(1)

前一篇我們提到了元件的內聚,以及用來衡量內聚性的三個標準,CCP、CRP與REP。

在這篇中,我們要來繼續談談軟體的耦合性。

元件的耦合是指元件與其他模組或元件之間的相依性,越大的話代表該元件與其他元件的相依性越大。

接下來在元件的耦合性中,以下這三個原則則是用來處理元件之間的關係。

無環依賴原則(AOP)

無環依賴原則的意義在於,在元件的依賴圖關係中不允許出現環

乍聽之下,似乎有點抽象,甚麼是環呢?

讓我們來看一下這張圖﹔

在右下角處,Authorizer、Interactors、Entities 形成了一個閉鎖的環狀依賴,我們就稱這個狀況為依賴環。

箭頭的方向也是有意義的,尖的地方代表被依賴者,沒有尖的地方則代表依賴者,舉例來說,Entities 中用到了 Authorizer 中的類別,所以方向會朝向Authorizer。

raw-image

不過,依賴環會產生甚麼問題呢?

如果要測試 Entities 元件的話,我們必須要建置 Authorizer 和 Interactors,由於它是環狀依賴,使其很難確定元件建置的順序,這種環也使得我們非常難以對元件進行隔離。

如此以來,「隔天早上綜合症(Morning-after syndrome)」便會很常發生,當你今天下班前開心的把某個元件的 change 推上 code repo,結果隔天來發現你的系統突然壞了,原因是因為前一天還有人比你晚走,並改了你所依賴的某個函式庫。

解除依賴環

要解除元件之間的依賴環,並把其恢復為有像無環圖,可以使用下面這兩種方法。

依賴反向原則(Dependency-Inversion Principle) :

根據上面的情況,假設在 Entities 中的 User 類別用到了 Authorizer 中的類別,我們可以建立一個具有 User 所需物件的介面,並把其放入 Entities 中,讓Authorizer 中的該類別繼承它,由此而來便反向了 Entities 與 Authorizer 之間的依賴關係,解除了依賴環。

raw-image

新建立一個 Entities 和 Authorizer 都依賴的元件,把 Entities 和 Authorizer 都依賴的類別移動到這個元件中。

raw-image

兩種方法個人覺得其實概念上相當接近,都是想反轉 Authorzer 與 Entities 之間的依賴關係,差別在於是否要把依賴的元件獨立出來,這就回到了上一篇所講的元件內聚性,我們在評估反轉依賴時就要考量,是否有必要把元件獨立出來呢? 亦或者把介面抽離出來即可?

讀到這裡,我們就必須要了解:

  • High level modules should not depend upon low level modules. Both should depend upon abstrations. (高階模組不能相依於低階模組,而兩者都要相依於抽象)
  • Abstractions should not depend upon details. Details should depend upon abstractions. (抽象不可以相依於細節,而細節必須相依於抽象)

通常在做系統分析與設計時,當看系統的高度升高時,就能看到各元件的流程,再升高一點,就能看到系統之間的整合方式,再升高一點,就能看到整個系統的架構,所謂的高階 (High level),是指以系統分析的高度,將架構內各個具體的細節加以抽象化到較高的位置時,它就是高階物件,而當元件開始擺脫細節的時候,設計者就會抽絲剝繭將共同的特性抽出來,這時設計的位階就會逐步增加,當增加到不能再增加的時候,抽象就形成了。

在抽象的形成過程中,系統分析師基本上會檢視要抽象的成員抽出的程度,據以調整細節設計,因此細節會自然的相依於抽象,這些細節的部份的位階就會較低,因此也被稱為低階 (Low level) 物件,這類物件通常稱為具體類別 (Concrete Class)。

有興趣的讀者可以參考這篇文章,有更多的敘述。

看了一堆文字敘述,我想大家應該頭有點昏吧,這裡,Grant 也準備了一些例子。

public class ReportStatistic() {     
  private SQLAccess _access = new SQLAccess();
  public double Sum() {
  var items = _access.GetAllCost();
    ...
  } }
  public class SQLAccess {
  public List<CostItem> GetAllCost() {
...
  }
}

在 ReportStatistic 方法中,我們可以看到整個系統的流程,代表著它就是高階模組,而我們也看到了 ReportStatstic 方法與負責實作 SQL 存取的細節方法 SQLAccess 方法有著緊密的相依性。

當今天系統規模一變大,很可能這種高耦合的狀況就會變得更加嚴重,一旦改了低階模組,連帶高階模組都必須要受到更動。

因此,在類別中,不應該直接使用另一個具有實作類別,而是要依賴抽象的介面,去承接繼承該介面的實作類別。它的目標就是解除物件與物件間,兩者的直接相依關係。

如果將上面的ReportStatistic方法改為依賴於抽象,就會變得好維護許多。

public class ReportStatistic()
{
private IAccess _access = new SQLAccess();

public double Sum()
{
var items = _access.GetAllCost();
...
}
}

public interfalce IAccess
{
List<CostItem GetAllCost();
}

public class SQLAccess : IAccess
{
public List<CostItem> GetAllCost()
{
...
}
}

穩定依賴原則(SDP)

穩定依賴原則的意義在於我們要讓依賴關係朝著穩定的方向進行依賴。

之前提及的共同封閉原則中,我們提及某些元件被設計成是可變的,某些則是必須要被封閉的,只因為沒有 100% 的封閉,但我們不想要在上change的同時,所有的元件都會受到影響。

因此,對於任何元件而言,如果預期它是可變的,就不應該也不要讓一個難以更改的元件依賴它。

元件穩定性

不過,我們要如何去評估一個元件是不是穩定的呢?

一個可行的方法是「讓許多元件依賴它」,一個穩定的元件是相對不容易被改變的,同時也必須承擔被依賴的責任。

針對下圖中的 X,我們便可以稱其為穩定的元件,因為它對三個元件具有責任,而 X 本身不依賴任何元件,也代表著任何外部因素都不會讓其改變。

raw-image

同樣的,我們也來看看甚麼是不穩定的元件。
下圖中的 Y 依賴三個外部元件,且本身不被任何元件依賴,不用負擔任何責任。

raw-image

計算元件穩定性的度量

  • Fan-In: 輸入依賴度,代表說多少外部元件依賴此元件內部的類別
  • Fan-Out: 輸出依賴度,代表多少此元件內部的類別依賴於別人
  • Instability: 不穩定性 I = (FO)/(FI+FO) ,介於[0,1],0代表非常穩定 1代表非常不穩定,代表沒有任何元件依賴於該元件

計算元件穩定性的方式也很簡單,下圖中的Cc的穩定性為 1/(1+3) = 1/4。

raw-image

不過,實際在設計系統時,我們不會希望所有的元件都是穩定的,我們希望有些元件是不穩定、有些是穩定的,而穩定與不穩定的元件的配額就必須要參照實際情況來決定了。

穩定抽象原則(SAP)

穩定抽象原則秉持著「一個穩定的元件也應該是抽象的」,這樣它的穩定性就不會影響它擴展。

會這樣是因為雖然我們不希望穩定模組經常被改變,不過還是有些特殊情形,我們希望穩定元件可以以穩定的方式進行擴展。

SAP 規定穩定性意味著「抽象性」,因此,依賴應朝向「抽象」的方向進行。

若定義了不穩定性I 以及抽象性A(Na/Nc) ,我們可以建立一個以I為橫軸 A為縱軸的座標圖:

Na(抽象類別及介面的總數)

Nc(類別總數)

raw-image

最穩定的地方是座標圖的左上,最不穩定的地方是右下,但要注意的是,並不是所有元件都只能落在這兩個地方,因為元件的抽象性跟依賴性有程度之分,例如,抽象類別也可能依賴於其他的抽象類別。

但即便如此,還是有一些是元件不應該在的地方,分別是右上角的無用地帶,以及左下角的痛苦地帶。

raw-image

無用地帶

若落於這個地方,代表元件有著最大的抽象,但卻沒有任何人依賴它,若無來依賴於它,那根本毫無用武之地。

痛苦地帶

在這裡的元件是具有高度穩定且具體的元件,我們不想要這種元件,因為其太具體所以很難進行擴展。

因此,盡可能避免落於無用以及痛苦地帶後,元件便會落於主序列上(Main Sequence)。

在主序列上的元件既不是太穩定、也並非太抽象,而最佳的端點則落於主序列的兩個頂點上,但這終究是不太可能達成的,落於主序列上的元件已經算是很不錯了。

最後,我們也許也有可能會希望微調目前系統上的元件,這個時候就必須要評估元件到主序列的距離,然後對其進行調整。

raw-image

總結

這章的內容對於軟體及系統架構的設計非常有幫助,我們可以透過這些實際度量的準則設計未來的系統或評估目前的系統,看到這些內容,Grant也不禁恍然大悟,有時候在工作上,較資深的工程師會提點我們要小心依賴的方向,以及嚴格控管依賴,Grant 之前也許在不經意之中,讓系統形成了依賴環。

因此,在撰寫程式碼或設計系統前,也許考慮的多一點,就可以讓日後的維護變得相對輕鬆一點。

本文同步發表於格蘭特的部落格


參考資料


留言
avatar-img
留言分享你的想法!
avatar-img
格蘭特的沙龍
14會員
18內容數
還在為不知道怎麼面試而煩惱嗎? 還在為苦無面試機會而沮喪嗎? 別擔心~讓我們一起面對! 在專題中,我將以自身經驗傳授如何撰寫履歷以及分享面試經驗。
格蘭特的沙龍的其他內容
2023/10/26
嗨!我是格蘭特,歡迎來到軟體工程師面試大哉問! 近二十家公司面試經驗大公開,本篇是系列文的第八篇。 這篇將繼續分享上一篇在遊戲橘子的面試經驗。
Thumbnail
2023/10/26
嗨!我是格蘭特,歡迎來到軟體工程師面試大哉問! 近二十家公司面試經驗大公開,本篇是系列文的第八篇。 這篇將繼續分享上一篇在遊戲橘子的面試經驗。
Thumbnail
2023/07/18
嗨!我是格蘭特,歡迎來到軟體工程師面試大哉問! 近二十家公司面試經驗大公開,本篇是系列文的第七篇。 格蘭特這次要分享的是在遊戲領域中的佼佼者,自產及代理多種知名遊戲的遊戲橘子(Gamania)。 本次機會是由 Headhunter 在 LinkedIn 主動聯繫,職位是資深後端工程師。 由於篇
Thumbnail
2023/07/18
嗨!我是格蘭特,歡迎來到軟體工程師面試大哉問! 近二十家公司面試經驗大公開,本篇是系列文的第七篇。 格蘭特這次要分享的是在遊戲領域中的佼佼者,自產及代理多種知名遊戲的遊戲橘子(Gamania)。 本次機會是由 Headhunter 在 LinkedIn 主動聯繫,職位是資深後端工程師。 由於篇
Thumbnail
2023/04/06
歡迎來到格蘭特的私房好書,本文是專題系列文的第二篇。 因工作上有些事情需要處理,讓這個專題延宕了幾個月,希望大家不要見怪 :D。 今天要與大家一同閱讀的是 Annette Simmons 所著的《你的團隊需要一個會說故事的人》。 作者:Annette Simmons (安奈特.西蒙斯) 出版社:先覺
Thumbnail
2023/04/06
歡迎來到格蘭特的私房好書,本文是專題系列文的第二篇。 因工作上有些事情需要處理,讓這個專題延宕了幾個月,希望大家不要見怪 :D。 今天要與大家一同閱讀的是 Annette Simmons 所著的《你的團隊需要一個會說故事的人》。 作者:Annette Simmons (安奈特.西蒙斯) 出版社:先覺
Thumbnail
看更多
你可能也想看
Thumbnail
還在煩惱平凡日常該如何增添一點小驚喜嗎?全家便利商店這次聯手超萌的馬來貘,推出黑白配色的馬來貘雪糕,不僅外觀吸睛,層次豐富的雙層口味更是讓人一口接一口!本文將帶你探索馬來貘雪糕的多種創意吃法,從簡單的豆漿燕麥碗、藍莓果昔,到大人系的奇亞籽布丁下午茶,讓可愛的馬來貘陪你度過每一餐,增添生活中的小確幸!
Thumbnail
還在煩惱平凡日常該如何增添一點小驚喜嗎?全家便利商店這次聯手超萌的馬來貘,推出黑白配色的馬來貘雪糕,不僅外觀吸睛,層次豐富的雙層口味更是讓人一口接一口!本文將帶你探索馬來貘雪糕的多種創意吃法,從簡單的豆漿燕麥碗、藍莓果昔,到大人系的奇亞籽布丁下午茶,讓可愛的馬來貘陪你度過每一餐,增添生活中的小確幸!
Thumbnail
不管用哪種語言開發軟體,除非是那種一個 function 寫個幾萬行的人 (來人啊,把這種人拖去砍了),不然,一般都會根據某些因素,切割成模組或是特定功能的區塊 (一個 class 或是一個 function),但要完成一個特定功能,這些模組或區塊勢必要一起合作,因此這些模組與區塊就發生了關係。
Thumbnail
不管用哪種語言開發軟體,除非是那種一個 function 寫個幾萬行的人 (來人啊,把這種人拖去砍了),不然,一般都會根據某些因素,切割成模組或是特定功能的區塊 (一個 class 或是一個 function),但要完成一個特定功能,這些模組或區塊勢必要一起合作,因此這些模組與區塊就發生了關係。
Thumbnail
不重新造輪子,我們使用第三方函式庫,聽起來很合理,但每個被引入的函式庫意味著一種 coupling,看到套件管理工具下載眾多第三方函式庫,意味著不用重寫這些東西,開發效率能提升數倍甚至數百倍,但我們真的都能掌握這些 coupling 嗎?當這其中任何一個環節出錯,我們的系統架構真的很優雅地應付嗎?
Thumbnail
不重新造輪子,我們使用第三方函式庫,聽起來很合理,但每個被引入的函式庫意味著一種 coupling,看到套件管理工具下載眾多第三方函式庫,意味著不用重寫這些東西,開發效率能提升數倍甚至數百倍,但我們真的都能掌握這些 coupling 嗎?當這其中任何一個環節出錯,我們的系統架構真的很優雅地應付嗎?
Thumbnail
這是 30 天寫作挑戰的第 16 天。今天要跟大家分享的主題是:3 個學習前端時,重要的程式框架
Thumbnail
這是 30 天寫作挑戰的第 16 天。今天要跟大家分享的主題是:3 個學習前端時,重要的程式框架
Thumbnail
這篇文章將會講述 Unity C# 中關於 Interface (介面/接口)的基本介紹以及原理說明,最後提供完整的使用流程。
Thumbnail
這篇文章將會講述 Unity C# 中關於 Interface (介面/接口)的基本介紹以及原理說明,最後提供完整的使用流程。
Thumbnail
本篇文章將會繼續上一篇的內容,還沒看過上一篇的朋友,記得先看完上一篇再繼續本篇的閱讀唷。 連結﹔談談軟體元件的內聚與耦合(1) 前一篇我們提到了元件的內聚,以及用來衡量內聚性的三個標準,CCP、CRP與REP。 在這篇中,我們要來繼續談談軟體的耦合性。
Thumbnail
本篇文章將會繼續上一篇的內容,還沒看過上一篇的朋友,記得先看完上一篇再繼續本篇的閱讀唷。 連結﹔談談軟體元件的內聚與耦合(1) 前一篇我們提到了元件的內聚,以及用來衡量內聚性的三個標準,CCP、CRP與REP。 在這篇中,我們要來繼續談談軟體的耦合性。
Thumbnail
近期花了一些時間研讀 Robert C. Martin 所著的 Clean Architecture 這本書,剛好看到了一些概念恰巧可以與工作上遇到的架構做一些印證,於是便想寫一些文章做一些紀錄。 在 Clean Architecture 中,提到了軟體元件的內聚與耦合的概念。
Thumbnail
近期花了一些時間研讀 Robert C. Martin 所著的 Clean Architecture 這本書,剛好看到了一些概念恰巧可以與工作上遇到的架構做一些印證,於是便想寫一些文章做一些紀錄。 在 Clean Architecture 中,提到了軟體元件的內聚與耦合的概念。
追蹤感興趣的內容從 Google News 追蹤更多 vocus 的最新精選內容追蹤 Google News