類別的SOLID原則 SOLID



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

2. O:開閉原則Open/Closed Principle (OCP)

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

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

#5. D: 依賴倒置原則Dependency Inversion Principle (DIP)

SOLID

SOLID 是Michael Feathers推薦的便於記憶的首字母簡寫,它代表了Robert Martin命名的最重要的五個面對對象編碼設計原則

  • S: 單一職責原則(SRP)

  • O: 開閉原則(OCP)

  • L: 里氏替換原則(LSP)

  • I: 介面隔離原則(ISP)

  • #D: 依賴倒置原則(DIP)

1. 單一職責原則

Single Responsibility Principle (SRP)

正如在Clean Code所述,"修改一個類別應該只為一個理由"。人們總是易於用一堆方法塞滿一個類,如同我們只能在飛機上 只能攜帶一個行李箱(把所有的東西都塞到箱子裡)。這樣做 的問題是:從概念上這樣的類別不是高內聚的,並且留下了很多 理由去修改它。將你需要修改類別的次數降低到最小很重要。這是因為,當有很多方法在類別中時,修改其中一處,你很難知 曉在程式碼庫中哪些依賴的模組會被影響。

壞:

class UserSettings
{
    private $user;
 
    public function __construct(User $user)
    {
        $this->user = $user;
    }
 
    public function changeSettings(array $settings): void
    {
        if ($this->verifyCredentials()) {
            // ...
        }
    }
 
    private function verifyCredentials(): bool
    {
        // ...
    }
}

好:

class UserAuth
{
    private $user;
 
    public function __construct(User $user)
    {
        $this->user = $user;
    }
    
    public function verifyCredentials(): bool
    {
        // ...
    }
}
 
class UserSettings
{
    private $user;
    private $auth;
 
    public function __construct(User $user)
    {
        $this->user = $user;
        $this->auth = new UserAuth($user);
    }
 
    public function changeSettings(array $settings): void
    {
        if ($this->auth->verifyCredentials()) {
            // ...
        }
    }
}

2. 開閉原則

Open/Closed Principle (OCP)

#如Bertrand Meyer所述,"軟體的工件( classes, modules, functions 等)應該對擴展開放,對修改關閉。" 然而這句話意味著什麼呢?這個原則大體上表示你應該允許在不改變已有程式碼的情況下增加新的功能

壞:

abstract class Adapter
{
    protected $name;
 
    public function getName(): string
    {
        return $this->name;
    }
}
 
class AjaxAdapter extends Adapter
{
    public function __construct()
    {
        parent::__construct();
 
        $this->name = 'ajaxAdapter';
    }
}
 
class NodeAdapter extends Adapter
{
    public function __construct()
    {
        parent::__construct();
 
        $this->name = 'nodeAdapter';
    }
}
 
class HttpRequester
{
    private $adapter;
 
    public function __construct(Adapter $adapter)
    {
        $this->adapter = $adapter;
    }
 
    public function fetch(string $url): Promise
    {
        $adapterName = $this->adapter->getName();
 
        if ($adapterName === 'ajaxAdapter') {
            return $this->makeAjaxCall($url);
        } elseif ($adapterName === 'httpNodeAdapter') {
            return $this->makeHttpCall($url);
        }
    }
 
    private function makeAjaxCall(string $url): Promise
    {
        // request and return promise
    }
 
    private function makeHttpCall(string $url): Promise
    {
        // request and return promise
    }
}

好:

interface Adapter
{
    public function request(string $url): Promise;
}
 
class AjaxAdapter implements Adapter
{
    public function request(string $url): Promise
    {
        // request and return promise
    }
}
 
class NodeAdapter implements Adapter
{
    public function request(string $url): Promise
    {
        // request and return promise
    }
}
 
class HttpRequester
{
    private $adapter;
 
    public function __construct(Adapter $adapter)
    {
        $this->adapter = $adapter;
    }
 
    public function fetch(string $url): Promise
    {
        return $this->adapter->request($url);
    }
}

 

3. 里氏替換原則

Liskov Substitution Principle (LSP)

#這是一個簡單的原則,卻用了一個不好理解的術語。它的正式定義是"如果S是T的子類型,那麼在不改變程式原有既定屬性(檢查、執行任務等)的前提下,任何T類型的物件都可以使用S類型的物件替代(例如,使用S的物件可以取代T的物件)" 這個定義比較難理解:-)。

對這個概念最好的解釋是:如果你有一個父類和一個子類,在不改變 原有結果正確性的前提下父類和子類可以互換。這聽起來依舊讓人 有些迷惑,所以讓我們來看一個經典的正方形-長方形的例子。從數學 上講,正方形是一種長方形,但是當你的模型通過繼承使用了"is-a" 的關係時,就不對了。

壞:

class Rectangle
{
    protected $width = 0;
    protected $height = 0;
 
    public function setWidth(int $width): void
    {
        $this->width = $width;
    }
 
    public function setHeight(int $height): void
    {
        $this->height = $height;
    }
 
    public function getArea(): int
    {
        return $this->width * $this->height;
    }
}
 
class Square extends Rectangle
{
    public function setWidth(int $width): void
    {
        $this->width = $this->height = $width;
    }
 
    public function setHeight(int $height): void
    {
        $this->width = $this->height = $height;
    }
}
 
function printArea(Rectangle $rectangle): void
{
    $rectangle->setWidth(4);
    $rectangle->setHeight(5);
 
    // BAD: Will return 25 for Square. Should be 20.
    echo sprintf('%s has area %d.', get_class($rectangle), $rectangle->getArea()).PHP_EOL;
}
 
$rectangles = [new Rectangle(), new Square()];
 
foreach ($rectangles as $rectangle) {
    printArea($rectangle);
}

好:

最好是將這兩個四邊形分別對待,用一個適合兩種類型的更通用子類型來代替。

儘管正方形和長方形看起來很相似,但他們是不同的。正方形更接近菱形,而長方形更接近平行四邊形。但他們不是子類型。儘管相似,正方形、長方形、菱形、平行四邊形都是有自己屬性的不同形狀。

interface Shape
{
    public function getArea(): int;
}
 
class Rectangle implements Shape
{
    private $width = 0;
    private $height = 0;
 
    public function __construct(int $width, int $height)
    {
        $this->width = $width;
        $this->height = $height;
    }
 
    public function getArea(): int
    {
        return $this->width * $this->height;
    }
}
 
class Square implements Shape
{
    private $length = 0;
 
    public function __construct(int $length)
    {
        $this->length = $length;
    }
 
    public function getArea(): int
    {
        return $this->length ** 2;
    }
}
 
function printArea(Shape $shape): void
{
    echo sprintf('%s has area %d.', get_class($shape), $shape->getArea()).PHP_EOL;
}
 
$shapes = [new Rectangle(4, 5), new Square(5)];
 
foreach ($shapes as $shape) {
    printArea($shape);
}

4. 介面隔離原則

Interface Segregation Principle (ISP)

介面隔離原則表示:"呼叫方不應該被強制依賴他不需要的介面"

有一個清晰的例子來說明示範這條原則。當一個類別需要一個大量的設定項, 為了方便不會要求呼叫方去設定大量的選項,因為在通常他們不需要所有的 設定項。讓設定項目可選有助於我們避免產生"胖介面"

壞:

interface Employee
{
    public function work(): void;
 
    public function eat(): void;
}
 
class HumanEmployee implements Employee
{
    public function work(): void
    {
        // ....working
    }
 
    public function eat(): void
    {
        // ...... eating in lunch break
    }
}
 
class RobotEmployee implements Employee
{
    public function work(): void
    {
        //.... working much more
    }
 
    public function eat(): void
    {
        //.... robot can't eat, but it must implement this method
    }
}

好:

不是每一個工人都是僱員,但是每一個僱員都是一個工人

interface Workable
{
    public function work(): void;
}
 
interface Feedable
{
    public function eat(): void;
}
 
interface Employee extends Feedable, Workable
{
}
 
class HumanEmployee implements Employee
{
    public function work(): void
    {
        // ....working
    }
 
    public function eat(): void
    {
        //.... eating in lunch break
    }
}
 
// robot can only work
class RobotEmployee implements Workable
{
    public function work(): void
    {
        // ....working
    }
}

5. 依賴倒置原則

Dependency Inversion Principle (DIP)

這條原則說明兩個基本的要點:

高階的模組不應該依賴低階的模組,它們都應該依賴抽象

##抽像不應該依賴實現,實現應該依賴抽象

這條起初看起來有點晦澀難懂,但是如果你使用過PHP 框架(例如Symfony),你應該見過依賴注入(DI),它是對這個概念的實現。雖然它們不是完全相等的概念,依賴倒置原則使高階模組 與低階模組的實作細節和創建分離。可以使用依賴注入(DI)這種方式來實現它。最大的好處 是它使模組之間解耦。耦合會導致你難於重構,它是一種非常糟糕的開發模式。

壞:

class Employee
{
    public function work(): void
    {
        // ....working
    }
}
 
class Robot extends Employee
{
    public function work(): void
    {
        //.... working much more
    }
}
 
class Manager
{
    private $employee;
 
    public function __construct(Employee $employee)
    {
        $this->employee = $employee;
    }
 
    public function manage(): void
    {
        $this->employee->work();
    }
}

好:

interface Employee
{
    public function work(): void;
}
 
class Human implements Employee
{
    public function work(): void
    {
        // ....working
    }
}
 
class Robot implements Employee
{
    public function work(): void
    {
        //.... working much more
    }
}
 
class Manager
{
    private $employee;
 
    public function __construct(Employee $employee)
    {
        $this->employee = $employee;
    }
 
    public function manage(): void
    {
        $this->employee->work();
    }
}