開發物件導向概念
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);
}
}
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。