首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 綜合教學

設計模式六大核心原則深度解析:單一職責、里氏替換、介面隔離、依賴倒置、迪米特、開閉原則

     編輯:Java 綜合教學

前言

設計模式是「經驗的總結」,而六大原則是設計模式的「理論基石」。掌握這六大原則,才能在具體場景中靈活選擇、組合模式,寫出**高內聚、低耦合、易擴展、可維護**的軟體。

1. 單一職責原則 (Single Responsibility Principle, SRP)

定義

一個類應該只有一個引起它變化的原因,即**只負責一項職責**。

反例

class Calculator {
    public int add(String filePath) throws IOException {
        // 1. 讀取檔案
        BufferedReader br = new BufferedReader(new FileReader(filePath));
        int a = Integer.parseInt(br.readLine());
        int b = Integer.parseInt(br.readLine());
        // 2. 計算
        return a + b;
    }
}

問題:`Calculator` 同時負責 **IO 讀取** 與 **計算邏輯**,變更檔案格式或計算規則都會修改此類。

正例

// 職責 1:資料讀取
class FileNumberReader {
    public Pair readTwoNumbers(String path) throws IOException {
        try (BufferedReader br = new BufferedReader(new FileReader(path))) {
            return new Pair<>(Integer.parseInt(br.readLine()), Integer.parseInt(br.readLine()));
        }
    }
}

// 職責 2:計算邏輯
class Calculator {
    public int add(int a, int b) { return a + b; }
    public int subtract(int a, int b) { return a - b; }
    public int multiply(int a, int b) { return a * b; }
}

// 使用
Pair nums = new FileNumberReader().readTwoNumbers("data.txt");
int result = new Calculator().add(nums.getFirst(), nums.getSecond());

效益

  • 類別職責單一,易理解、易測試、易複用。
  • 變更檔案格式只動 `FileNumberReader`,變更計算規則只動 `Calculator`。

2. 里氏替換原則 (Liskov Substitution Principle, LSP)

定義

子類型必須能夠替換其基類型,**且不影響程式正確性**。即:`Father f = new Son();` 任何使用 `Father` 的地方,換成 `Son` 都不應破壞行為契約。

反例

class Bird {
    public void fly() { System.out.println("飛行"); }
}

class Penguin extends Bird {
    @Override
    public void fly() { throw new UnsupportedOperationException("企鵝不會飛"); } // 破壞契約
}

使用 `Bird b = new Penguin(); b.fly();` 拋出異常,違反 LSP。

正例:介面隔離 + 組合

interface Flyable { void fly(); }
interface Swimmable { void swim(); }

class Sparrow implements Flyable { public void fly() { ... } }
class Penguin implements Swimmable { public void swim() { ... } }
// 不再強迫 Penguin 繼承 fly()

核心口訣

**子類只擴展功能,不改變父類行為契約** (前置條件不強於父類,後置條件不弱於父類)。

3. 介面隔離原則 (Interface Segregation Principle, ISP)

定義

客戶端不應依賴它不需要的介面。**介面應盡量細小、專一**,避免「胖介面」。

反例

interface MobilePhone {
    void call();
    void sendSms();
    void playGame(); // 老人機不需要
    void browseWeb(); // 功能機不需要
}

正例:拆分為角色介面

interface BasicPhone { void call(); void sendSms(); }
interface SmartPhone extends BasicPhone { void playGame(); void browseWeb(); }
interface CameraPhone { void takePhoto(); }

// 實作類只實作需要的介面
class FeaturePhone implements BasicPhone { ... }
class IPhone implements SmartPhone, CameraPhone { ... }

效益

  • 實作類不需實作空方法 (`throw new UnsupportedOperationException()`)。
  • 降低耦合,介面變更影響範圍最小化。

4. 依賴倒置原則 (Dependency Inversion Principle, DIP)

定義

  1. 高層模組不應依賴低層模組,**兩者都應依賴抽象** (介面/抽象類)。
  2. 抽象不應依賴細節,**細節應依賴抽象**。

反例:高層直接依賴低層具體類

class MySQLDatabase { public void save(String data) { ... } }
class UserService {
    private MySQLDatabase db = new MySQLDatabase(); // 緊耦合
    public void register(String user) { db.save(user); }
}

換資料庫需修改 `UserService`,違反 DIP。

正例:依賴抽象 + 依賴注入 (DI)

interface Database { void save(String data); }

class MySQLDatabase implements Database { public void save(String data) { ... } }
class PostgreSQLDatabase implements Database { public void save(String data) { ... } }

class UserService {
    private final Database database; // 依賴抽象
    public UserService(Database database) { this.database = database; } // 建構子注入
    public void register(String user) { database.save(user); }
}

// 組裝 (由外部容器/主程式決定具體實作)
UserService service = new UserService(new PostgreSQLDatabase());

效益

  • 高層業務邏輯 (`UserService`) 與低層實作 (`MySQLDatabase`) 解耦。
  • 單元測試可注入 Mock/Stub (`new UserService(mockDatabase)`)。
  • 符合「好萊塢原則」:Don't call us, we'll call you (框架呼叫應用程式碼)。

5. 迪米特法則 (Law of Demeter, LoD) / 最少知識原則

定義

一個物件應盡可能少地瞭解其他物件。**只與直接朋友 (依賴注入的、建立的、參數傳入的) 通訊,不與陌生人 (朋友的朋友) 通訊。**

反例:鏈式呼叫暴露內部結構

class Company {
    Department getDepartment() { ... }
}
class Department {
    Employee getManager() { ... }
}
class Employee {
    String getName() { ... }
}

// 客戶端違反 LoD
String name = company.getDepartment().getManager().getName(); // 穿透 3 層

正例:封裝委派

class Company {
    Department getDepartment() { ... }
    String getDepartmentManagerName() { // 封裝內部導航
        return getDepartment().getManager().getName();
    }
}

// 客戶端只知 Company
String name = company.getDepartmentManagerName();

效益

  • 降低類之間耦合,內部結構變更不影響外部。
  • 但過度封裝會導致「神類」膨脹,需平衡。

6. 開閉原則 (Open/Closed Principle, OCP)

定義

**對擴展開放,對修改關閉**。即:新增功能時,**不修改既有原始碼**,而是透過新增程式碼 (繼承、實作介面、組合) 實現。

反例:修改既有類新增功能

class ReportGenerator {
    public void generate(String type) {
        if ("PDF".equals(type)) { ... }
        else if ("EXCEL".equals(type)) { ... }
        // 新增 Word 需修改此方法 → 違反 OCP
    }
}

正例:策略模式 + 介面

interface ReportExporter { void export(ReportData data); }

class PdfExporter implements ReportExporter { public void export(ReportData data) { ... } }
class ExcelExporter implements ReportExporter { public void export(ReportData data) { ... } }
class WordExporter implements ReportExporter { public void export(ReportData data) { ... } } // 新增不動舊碼

class ReportGenerator {
    private final Map exporters; // 注入所有策略
    public void generate(String type, ReportData data) {
        exporters.get(type).export(data);
    }
}

效益

  • 既有程式碼穩定,不引入回歸風險。
  • 新功能獨立開發、測試、部署。

六大原則關係圖譜

原則核心關鍵字主要解決問題常配合模式
SRP單一職責類膨脹、職責混雜單例、工廠
LSP子類可替換父類繼承濫用、契約破壞模板方法、策略
ISP介面細小專一胖介面、空實作介面隔離、適配器
DIP依賴抽象、DI高層耦合低層、難測試依賴注入、工廠、策略
LoD最少知識過度耦合、鏈式呼叫外觀、中介者
OCP擴展開放、修改關閉頻繁修改舊碼、回歸風險策略、裝飾者、觀察者

結語

六大原則不是教條,而是**設計決策的指南針**。實務中常需權衡 (如過度遵循 OCP 導致類爆炸),但內化這些原則,能讓你在面對複雜需求時,做出**可預測、可維護、可演進**的架構選擇。建議從 **SRP + DIP** 入手,配合 **依賴注入框架 (Spring/CDI)** 落地,再逐步應用其他原則重構既有代碼。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved