軟體架構的23個基本原則

2024.04.28

軟體體系架構是基於一組適用於各種軟體系統的基本原則,有經驗的架構師知道這些原則,並且能夠在軟體產品的正確位置實現特定的原則。下面我們快速瀏覽一下架構師日常遵循的基本原則:

1. 依賴倒置(Dependency Inversion)

這原則顯示依賴的方向應該是抽象的,而不是具體實現。如果編譯時依賴在執行時間執行的方向上流動,就形成了直接依賴。透過依賴倒置,可以反轉依賴控制的方向。以下的文章更深入的討論了這個原則: How to apply SOLID Software Design Principles to Spring Boot Application (Part 5)[2]

2. 關注點分離(Separation of Concerns)

這項原則指出,軟體系統應該按照所做的工作類型來劃分。比方說可以依照業務邏輯、基礎設施或使用者介面劃分為不同的部分。透過將系統劃分為基於不同活動區域的不同部分,使得開發/測試/部署更加容易。 SoC是軟體架構模式(如領域驅動設計、六角形架構、整齊架構)背後的驅動力。

3. 控制反轉(Inversion of Control)

該原則類似於依賴倒置原則,但適用於更廣泛的背景。 IoC反轉了由不同的第三方框架(如Spring Framework)所管理的控制流程。與傳統Java EE程式(由開發工程師按程式初始化Beans)不同,Spring控制Bean的配置,這意味著控制倒置。

4. 依賴注入(Dependency Injection)

該原則意味著依賴應該在運行時透過建構函數注入。在下面的範例中,Action Interface透過HumanAction Implementation注入到Human類別中,從而決定在運行時實現哪個特定的動作。這種技術提供了控制依賴的靈活性:

package az.alizeynalli.di;

public interface Action {
    void do();
}

public class HumanAction implements Action {
 
    @Override
    public void do() {
        System.out.print("run");
    }
}

public class Human  {
     
    Action action;
     
    public Human(Action action) {
        this.action = action;
    }
 
    @Override
    public void do() {        
        actoin.do();        
    }
}

    public static void main(String[] args) {
        Human human = new Human(new HumanAction);
        human.do();
    }
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.

5. 單一職責(Single Responsibility)

此原則的主要想法是限定軟體系統的每個建構塊只承擔唯一的責任。無論建構塊的作用域是什麼,是插件、套件、類別、函數,甚至是變量,應該只有一個職責。這篇文章更深入的討論了這個原則: How to apply SOLID Software Design Principles to Spring Boot Application (Part 1)[3]

6. DRY(不要重複自己)

該原則旨在透過避免重複程式碼來消除冗餘。如果存在針對某些行為的現有功能,則應重複使用,而不是在多個實例中拷貝相同的程式碼片段。

每個知識片段在系統中都必須有單一、明確、權威的表示。

7. 開閉原則(Open-Closed)

軟體構件應該對擴充開放,對修改關閉。

這原理的簡單描述首先是由Bertrand Meyer提出的。每次都需要修改的軟體系統只會變得一團糟,而這種混亂的程式很容易在每次修改時出現錯誤。每個新功能都應該最大限度的增加新程式碼,最小限度減少舊程式碼的更改,理想情況下對舊程式碼的更改為零。

8. 持久化透明(Persistence Ignorance)

持久化透明的概念是,程式碼應該不受任何資料庫或持久性技術的影響。業務邏輯應該與任何技術無關。如果明天,有更好、更有效、更便宜的持久化技術,應該能夠以不影響上層抽象的方式改變系統的這一部分。

9. 亞格尼

You ain't gonna need it. 這項原則試圖避免軟體系統的過早優化。開發人員通常會在系統中過度設計一些東西,以期在將來的某個時候會有幫助,但這一刻往往不會到來。

10. 童子軍規則(Boy Scout Rule)

離開的時候要讓露營地比來的時候更乾淨。

這裡的主要想法是,當開發時遇到反模式,要堅持重構程式碼。隨著時間的推移,這會提高程式碼品質。

11. 里氏替換原則(Liskov-Subsititution)

如果對於每個類型為S的物件o1,都有一個類型為T的物件o2,這樣對於用T定義的所有程式P,當o1取代o2時,P的行為不變,那麼S就是T的子類型。

Barbara Liskov的這個定義可能聽起來很混亂,但本質上這個原則簡單易懂。如果重述上面的定義,該原則的意思是: 在使用繼承時,繼承的層次結構應該在功能和業務邏輯方面保持一致。子類別應該是可以相互替換的,並且不能改變父類別的行為。作為一個簡單的例子,可以用“臭名昭著的正方形/矩形”問題。其中正方形不應該是矩形的子類型,因為這兩個幾何形狀的高度和長度的定義是不同的(正方形的高度和長度是相等的,而矩形的高度和長度是不同的)。

12. 封裝(Encapsulation)

軟體系統的不同構建塊應該通過封裝來限制外界對其組件的訪問,可以通過在類範圍內設置組件為私有或在插件範圍內設置訪問限制來實現(就java而言),從而隱藏信息。

13. 鬆散耦合(Loose Coupling)

軟體架構中最重要的原則之一是鬆散耦合,這原則表明軟體系統的依賴關係應該鬆散,系統的一部分發生變化,對其他部分的影響應該最小。松耦合可以透過依賴倒置、非同步訊息中間件、事件來源等來實現。以下的文章深入探討了軟體工程中不同形式的耦合: 9 Forms of Coupling in Software Architecture[4]

14. 內聚(Cohesion)

內聚是指模組內的元素依賴的程度。某種意義上說,是對類別的方法和數據以及該類別所服務的某種統一目的或概念之間關係強度的度量。

建構高內聚的類別是一種最佳實踐,有利於實現單一責任原則、鬆散耦合等。

15. 介面隔離(Interface Segregation)

介面隔離原則指出,不應強迫客戶端依賴不使用的方法。

應該要明確的是,這個原則主要適用於靜態型別的程式語言,如Java、C等。在像Python或Ruby這樣的動態類型語言中,這個原則沒有太大意義。

可以想像這樣一種情況,我們的Income和Expense用例都依賴支援這兩種用例的業務邏輯功能。因此Income用例的許多依賴都和Expense用例相關,而Expense用例的依賴情況也有相同的問題。基於以上討論,ISP違規情況如下:

package az.alizeynalli.cashflow.core.service;

public interface ConverterService {
    Income convertIncome(Income income);
    Expense convertExpense(Expense expense);
}

@Component
public class ExpenseConverterServiceImpl implements ConverterService {

    @Override
    public Income convertIncome(Income income) {
        throw new UnsupportedOperationException();
    }

    @Override
    public Expense convertExpense(Expense expense) {
        // convert expense here
        return expense;
    }
}

@Component
public class IncomeConverterServiceImpl implements ConverterService {

    @Override
    public Income convertIncome(Income income) {
        // convert income here
        return income;
    }

    @Override
    public Expense convertExpense(Expense expense) {
        
        throw new UnsupportedOperationException();
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.

16. 限界上下文(Bounded Context)

限界上下文是領域驅動設計的中心模式。透過將大型應用程式或組織分解為單獨的概念模組,提供了一種處理複雜性的方法。每個概念模組代表一個上下文,該上下文與其他上下文分離(因此是有邊界的),並且可以獨立發展。理想情況下,每個限界上下文應該可以自由的為其中的概念選擇自己的名稱,並且應該獨佔的存取自己的持久化儲存。 [5]

17. 依賴穩定原則(Stable Dependencies)

這項原則指出,軟體系統的不同構建塊應該只依賴可靠、穩定的工件。這個原則在docker映像術語中更有意義,當我們從docker hub導入不同的依賴時,甚至不知道它們是否可靠/穩定。

18. 多態(Polymorphism)

這實際上屬於物件導向程式設計的4大支柱,鼓勵使用可以以多種形式提供的接口,多態性意味著具有多種形式的實體。

19. 模組化(Modularization)

模組化是將軟體系統劃分為多個獨立模組的過程,每個模組獨立工作。這項原則是應用於軟體系統靜態架構的單一職責分離原則的另一種形式。

20. 抽象(Abstraction)

這也屬於物件導向程式設計的四大支柱:

在研究物體或系統時去除物理的、空間的或時間的細節或屬性以集中註意力於更重要的部分,本質上與泛化過程相似。

21. KISS(保持簡單,愚蠢)

依照字面意思理解,這項原則激勵工程師保持程式碼簡單和愚蠢(容易理解),避免他人誤解。

22. 增量/迭代方法(Incremental/Iterative Approach)

這項原則是敏捷軟體開發宣言的基礎,基於軟體系統應該以漸進和迭代的方式開發的思想,每一次迭代都會增加系統功能並保證其運作。

23. 最少知識原則(Least Knowledge)

或叫資訊嫉妒(information envying),是封裝或資訊隱藏原則的另一個術語,規定軟體系統的不同部分應該只擁有需要的知識。