跳到主內容

開發物件導向概念

SOLID 是由 Robert C. Martin(Uncle Bob)提出的五大物件導向設計原則,能夠幫助開發人員寫出更加模組化、可擴展、可維護的程式碼。這些原則有助於降低耦合度、提高重用性,並減少系統的技術債。


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

定義:
一個類別應該只有一個職責(即變更的原因)。這表示每個類別應該只處理一件事情,否則會導致類別變得過於龐大,難以維護。

違反 SRP 的例子

PHP

class Report {
    public function generate() {
        return "Report content";
    }

    public function print() {
        echo "Printing report: " . $this->generate();
    }

    public function saveToFile() {
        file_put_contents("report.txt", $this->generate());
    }
}

這個 Report 類別同時負責產生報告列印報告儲存報告,違反了 SRP。

遵守 SRP 的改寫

PHP

class ReportGenerator {
    public function generate() {
        return "Report content";
    }
}

class ReportPrinter {
    public function print(ReportGenerator $report) {
        echo "Printing report: " . $report->generate();
    }
}

class ReportSaver {
    public function saveToFile(ReportGenerator $report) {
        file_put_contents("report.txt", $report->generate());
    }
}

現在,每個類別只有一個責任,產生報告列印報告儲存報告 被拆分成不同的類別,符合 SRP。

C#

public class ReportGenerator {
    public string Generate() {
        return "Report content";
    }
}

public class ReportPrinter {
    public void Print(ReportGenerator report) {
        Console.WriteLine("Printing report: " + report.Generate());
    }
}

public class ReportSaver {
    public void SaveToFile(ReportGenerator report) {
        File.WriteAllText("report.txt", report.Generate());
    }
}

 

這樣的設計讓每個類別只負責一個職責,提高可維護性。


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

定義:
軟體應該對擴展開放(可以新增功能),對修改封閉(不應該直接修改原始碼)。這意味著應該透過繼承介面來擴展功能,而不是直接修改現有的類別。

違反 OCP 的例子

PHP

class DiscountCalculator {
    public function applyDiscount($price, $type) {
        if ($type === "none") {
            return $price;
        } elseif ($type === "percentage") {
            return $price * 0.9;
        } elseif ($type === "fixed") {
            return $price - 10;
        }
        return $price;
    }
}

 

每次新增折扣類型時,都需要修改 applyDiscount 方法,違反 OCP。

遵守 OCP 的改寫

PHP

interface DiscountStrategy {
    public function applyDiscount($price);
}

class NoDiscount implements DiscountStrategy {
    public function applyDiscount($price) {
        return $price;
    }
}

class PercentageDiscount implements DiscountStrategy {
    public function applyDiscount($price) {
        return $price * 0.9;
    }
}

class FixedDiscount implements DiscountStrategy {
    public function applyDiscount($price) {
        return $price - 10;
    }
}

class DiscountCalculator {
    private $discountStrategy;

    public function __construct(DiscountStrategy $discountStrategy) {
        $this->discountStrategy = $discountStrategy;
    }

    public function calculate($price) {
        return $this->discountStrategy->applyDiscount($price);
    }
}

 

這樣,我們可以輕鬆新增新的折扣類別,而不需要修改 DiscountCalculator,符合 OCP。

C#

public interface IDiscountStrategy {
    decimal ApplyDiscount(decimal price);
}

public class NoDiscount : IDiscountStrategy {
    public decimal ApplyDiscount(decimal price) => price;
}

public class PercentageDiscount : IDiscountStrategy {
    public decimal ApplyDiscount(decimal price) => price * 0.9m;
}

public class FixedDiscount : IDiscountStrategy {
    public decimal ApplyDiscount(decimal price) => price - 10;
}

public class DiscountCalculator {
    private readonly IDiscountStrategy _discountStrategy;

    public DiscountCalculator(IDiscountStrategy discountStrategy) {
        _discountStrategy = discountStrategy;
    }

    public decimal Calculate(decimal price) {
        return _discountStrategy.ApplyDiscount(price);
    }
}
這樣我們可以自由地擴展折扣策略,而不影響 DiscountCalculator 內部的邏輯。

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

定義:
子類別應該可以替換父類別,且不會影響程式的正確性。

違反 LSP 的例子

PHP

class Bird {
    public function fly() {
        return "Flying";
    }
}

class Penguin extends Bird {
    public function fly() {
        throw new Exception("Penguins can't fly!");
    }
}

這違反了 LSP,因為 Penguin 無法真正地替換 Bird

遵守 LSP 的改寫

PHP

interface Flyable {
    public function fly();
}

class Sparrow implements Flyable {
    public function fly() {
        return "Flying";
    }
}

class Penguin {
    public function swim() {
        return "Swimming";
    }
}

這樣 Penguin 就不會被強迫擁有 fly() 方法,符合 LSP。


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

定義:
客戶端不應該被強迫實作它不需要的介面,而應該拆分成多個小介面。

違反 ISP 的例子

PHP

interface Worker {
    public function work();
    public function eat();
}

class Robot implements Worker {
    public function work() {
        return "Working";
    }

    public function eat() {
        throw new Exception("Robots don't eat!");
    }
}

 

Robot 被迫實作 eat() 方法,但它並不需要。

遵守 ISP 的改寫

PHP

interface Workable {
    public function work();
}

interface Eatable {
    public function eat();
}

class Human implements Workable, Eatable {
    public function work() {
        return "Working";
    }

    public function eat() {
        return "Eating";
    }
}

class Robot implements Workable {
    public function work() {
        return "Working";
    }
}

 

這樣 Robot 就不需要 eat() 方法,符合 ISP。


5. 依賴反轉原則(DIP - Dependency Inversion Principle)

定義:
高層模組不應該直接依賴低層模組,而應該依賴抽象。

遵守 DIP 的設計

interface Database {
    public function connect();
}

class MySQLDatabase implements Database {
    public function connect() {
        return "Connecting to MySQL";
    }
}

class Application {
    private $database;

    public function __construct(Database $database) {
        $this->database = $database;
    }

    public function run() {
        return $this->database->connect();
    }
}

這樣 Application 依賴的是 Database 介面,而非具體的 MySQLDatabase,符合 DIP。