物件導向設計的6大原則

gongchengship發表於2024-07-04

一.軟體設計的六大原則,通常被稱為SOLID原則,
是物件導向設計(OOD)中最重要的指導方針之一。這些原則旨在提高軟體的可維護性、可擴充套件性和可讀性。以下是SOLID原則的詳細解釋:

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

單一職責原則指出,一個類應該只有一個引起它變化的原因,即一個類應該只有一個職責。這樣可以減少類的複雜性,增加程式碼的可維護性。

示例:

class User {
    private String name;
    private String email;
    
    // getters and setters
}

class UserService {
    public void createUser(User user) {
        // 業務邏輯
    }
}

class UserRepository {
    public void saveUser(User user) {
        // 資料庫操作
    }
}

2. 開放/封閉原則 (Open/Closed Principle, OCP)

開放/封閉原則指出,軟體實體(類、模組、函式等)應該對擴充套件開放,對修改封閉。這意味著應透過擴充套件類的行為來實現功能的變化,而不是修改現有的程式碼。

示例:

abstract class Shape {
    abstract void draw();
}

class Circle extends Shape {
    void draw() {
        // 畫圓
    }
}

class Rectangle extends Shape {
    void draw() {
        // 畫矩形
    }
}

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

里氏替換原則指出,子類必須能夠替換掉它們的父類。這意味著子類應該在任何父類能夠出現的地方都能使用,並且行為不會改變。

示例:

class Rectangle {
    private int width;
    private int height;

    public void setWidth(int width) {
        this.width = width;
    }

    public void setHeight(int height) {
        this.height = height;
    }
}

class Square extends Rectangle {
    // 重寫方法確保正方形的特性
    @Override
    public void setWidth(int width) {
        super.setWidth(width);
        super.setHeight(width);
    }

    @Override
    public void setHeight(int height) {
        super.setWidth(height);
        super.setHeight(height);
    }
}

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

介面隔離原則指出,客戶端不應該被迫依賴它們不需要的介面。應將介面劃分為多個小介面,使每個介面只包含客戶端感興趣的方法。

示例:

interface Worker {
    void work();
}

interface Eater {
    void eat();
}

class Robot implements Worker {
    public void work() {
        // 機器人工作
    }
}

class Human implements Worker, Eater {
    public void work() {
        // 人類工作
    }

    public void eat() {
        // 人類吃飯
    }
}

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

依賴倒置原則指出,高層模組不應該依賴低層模組,二者都應該依賴其抽象。具體實現應該依賴抽象,而不是抽象依賴具體實現。

示例:

interface MessageService {
    void sendMessage(String message);
}

class EmailService implements MessageService {
    public void sendMessage(String message) {
        // 傳送電子郵件
    }
}

class SMSService implements MessageService {
    public void sendMessage(String message) {
        // 傳送簡訊
    }
}

class Notification {
    private MessageService messageService;

    public Notification(MessageService messageService) {
        this.messageService = messageService;
    }

    public void notify(String message) {
        messageService.sendMessage(message);
    }
}

6. 迪米特法則 (Law of Demeter, LoD)

迪米特法則,也稱為最少知識原則,指出一個物件應該對其他物件有儘可能少的瞭解。每個單元只應與它的直接朋友通訊,而不是與陌生人通訊。

示例:

class Engine {
    public void start() {
        // 啟動引擎
    }
}

class Car {
    private Engine engine;

    public Car() {
        this.engine = new Engine();
    }

    public void startCar() {
        engine.start();
    }
}

總結

  • 單一職責原則(SRP):每個類只有一個職責。
  • 開放/封閉原則(OCP):對擴充套件開放,對修改封閉。
  • 里氏替換原則(LSP):子類必須能夠替換掉它們的父類。
  • 介面隔離原則(ISP):使用多個專門的介面,而不是一個總介面。
  • 依賴倒置原則(DIP):依賴於抽象,而不是具體實現。
  • 迪米特法則(LoD):一個物件應對其他物件有儘可能少的瞭解。

這些原則共同幫助軟體設計者建立靈活、可維護、可擴充套件的系統。


二. 那麼還是以5.依賴倒置原則這個例子為基礎,違反依賴倒置的例子是什麼樣子?
違反依賴倒置原則(Dependency Inversion Principle, DIP)的例子通常是高層模組直接依賴於低層模組的實現細節,而不是依賴於抽象。這會導致程式碼的耦合度增加,難以維護和擴充套件。

違反依賴倒置原則的例子

問題描述

假設我們有一個通知系統(Notification System),它傳送訊息(Message)給使用者。我們有一個具體的郵件服務(EmailService)來傳送電子郵件。高層模組(Notification類)直接依賴於具體的郵件服務(EmailService)。

程式碼示例

// 具體的郵件服務類
class EmailService {
    public void sendEmail(String message) {
        // 傳送電子郵件的具體實現
        System.out.println("Sending email: " + message);
    }
}

// 高層模組,通知類
class Notification {
    private EmailService emailService;

    public Notification() {
        this.emailService = new EmailService();
    }

    public void notify(String message) {
        emailService.sendEmail(message);
    }
}

public class Main {
    public static void main(String[] args) {
        Notification notification = new Notification();
        notification.notify("Hello, this is a notification!");
    }
}

問題分析

  1. 高耦合Notification類直接依賴於具體的EmailService類。這使得Notification類與EmailService類緊密耦合。
  2. 難以擴充套件:如果我們想要增加一個新的訊息服務(如SMS服務),我們需要修改Notification類的程式碼。這違反了開放/封閉原則(OCP),使得系統難以擴充套件。
  3. 難以測試:在單元測試中,我們無法輕易替換EmailService,因為Notification類直接建立了EmailService的例項。

解決方案:依賴倒置原則

透過依賴倒置原則,我們可以重構程式碼,使高層模組依賴於抽象,而不是具體實現。

重構後的程式碼示例

// 抽象的訊息服務介面
interface MessageService {
    void sendMessage(String message);
}

// 具體的郵件服務類實現介面
class EmailService implements MessageService {
    public void sendMessage(String message) {
        // 傳送電子郵件的具體實現
        System.out.println("Sending email: " + message);
    }
}

// 高層模組,通知類依賴於抽象介面
class Notification {
    private MessageService messageService;

    public Notification(MessageService messageService) {
        this.messageService = messageService;
    }

    public void notify(String message) {
        messageService.sendMessage(message);
    }
}

public class Main {
    public static void main(String[] args) {
        // 依賴注入具體實現
        MessageService emailService = new EmailService();
        Notification notification = new Notification(emailService);
        notification.notify("Hello, this is a notification!");
    }
}

重構後的優點

  1. 低耦合Notification類不再直接依賴於具體的EmailService類,而是依賴於MessageService介面。這降低了類之間的耦合度。
  2. 易於擴充套件:要增加新的訊息服務(如SMS服務),只需實現MessageService介面,並在建立Notification例項時傳遞新的實現即可,無需修改現有的Notification類程式碼。
  3. 易於測試:在單元測試中,可以輕鬆地替換MessageService介面的實現,使用模擬(mock)物件來測試Notification類。

這裡,違反依賴倒置原則的程式碼示例展示了高層模組直接依賴低層模組的實現細節,從而導致高耦合和低擴充套件性。透過引入抽象介面並依賴於這些介面,可以遵循依賴倒置原則,提高程式碼的靈活性、可維護性和可測試性。


三.那麼還是以6. 迪米特法則中這個例子為基礎,違反迪米特法則的例子是什麼樣子?
違反依賴倒置原則(Dependency Inversion Principle, DIP)的例子通常是高層模組直接依賴於低層模組的實現細節,而不是依賴於抽象。這會導致程式碼的耦合度增加,難以維護和擴充套件。

違反依賴倒置原則的例子

問題描述

假設我們有一個通知系統(Notification System),它傳送訊息(Message)給使用者。我們有一個具體的郵件服務(EmailService)來傳送電子郵件。高層模組(Notification類)直接依賴於具體的郵件服務(EmailService)。

程式碼示例

// 具體的郵件服務類
class EmailService {
    public void sendEmail(String message) {
        // 傳送電子郵件的具體實現
        System.out.println("Sending email: " + message);
    }
}

// 高層模組,通知類
class Notification {
    private EmailService emailService;

    public Notification() {
        this.emailService = new EmailService();
    }

    public void notify(String message) {
        emailService.sendEmail(message);
    }
}

public class Main {
    public static void main(String[] args) {
        Notification notification = new Notification();
        notification.notify("Hello, this is a notification!");
    }
}

問題分析

  1. 高耦合Notification類直接依賴於具體的EmailService類。這使得Notification類與EmailService類緊密耦合。
  2. 難以擴充套件:如果我們想要增加一個新的訊息服務(如SMS服務),我們需要修改Notification類的程式碼。這違反了開放/封閉原則(OCP),使得系統難以擴充套件。
  3. 難以測試:在單元測試中,我們無法輕易替換EmailService,因為Notification類直接建立了EmailService的例項。

解決方案:依賴倒置原則

透過依賴倒置原則,我們可以重構程式碼,使高層模組依賴於抽象,而不是具體實現。

重構後的程式碼示例

// 抽象的訊息服務介面
interface MessageService {
    void sendMessage(String message);
}

// 具體的郵件服務類實現介面
class EmailService implements MessageService {
    public void sendMessage(String message) {
        // 傳送電子郵件的具體實現
        System.out.println("Sending email: " + message);
    }
}

// 高層模組,通知類依賴於抽象介面
class Notification {
    private MessageService messageService;

    public Notification(MessageService messageService) {
        this.messageService = messageService;
    }

    public void notify(String message) {
        messageService.sendMessage(message);
    }
}

public class Main {
    public static void main(String[] args) {
        // 依賴注入具體實現
        MessageService emailService = new EmailService();
        Notification notification = new Notification(emailService);
        notification.notify("Hello, this is a notification!");
    }
}

重構後的優點

  1. 低耦合Notification類不再直接依賴於具體的EmailService類,而是依賴於MessageService介面。這降低了類之間的耦合度。
  2. 易於擴充套件:要增加新的訊息服務(如SMS服務),只需實現MessageService介面,並在建立Notification例項時傳遞新的實現即可,無需修改現有的Notification類程式碼。
  3. 易於測試:在單元測試中,可以輕鬆地替換MessageService介面的實現,使用模擬(mock)物件來測試Notification類。

違反依賴倒置原則的程式碼示例展示了高層模組直接依賴低層模組的實現細節,從而導致高耦合和低擴充套件性。透過引入抽象介面並依賴於這些介面,可以遵循依賴倒置原則,提高程式碼的靈活性、可維護性和可測試性。

相關文章