## SOLID
**SOLID** 是Michael Feathers推薦的便于記憶的首字母簡寫,它代表了Robert Martin命名的最重要的五個面對對象編碼設計原則
* S: 職責單一原則 (SRP)-[相關資料](https://segmentfault.com/a/1190000013100807?utm_source=tag-newest)
* O: 開閉原則 (OCP)-[相關資料](https://segmentfault.com/a/1190000013123183?utm_source=tag-newest)
* L: 里氏替換原則 (LSP)-[相關資料](https://segmentfault.com/a/1190000013208730?utm_source=tag-newest)
* I: 接口隔離原則 (ISP)-[相關資料](https://segmentfault.com/a/1190000013208721?utm_source=tag-newest)
* D: 依賴反轉原則 (DIP)-[相關資料](https://segmentfault.com/a/1190000012929864?utm_source=tag-newest)
### 職責單一原則 Single Responsibility Principle (SRP)
正如在Clean Code所述“修改一個類應該只為一個理由”。人們總是習慣性地將一堆方法塞滿一個類,引發的問題是:從概念上這樣的類不是高內聚的,并且留下了很多理由去修改它,而將你需要修改類的次數降低到最小很重要。
**差:**
```php
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
{
// ...
}
}
```
**優:**
```php
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()) {
// ...
}
}
}
```
### 開閉原則 Open/Closed Principle (OCP)
正如Bertrand Meyer所述“軟件的工件(Classes、Modules、Functions等)應該對擴展開放,對修改關閉。”,這個原則大體上表示你應該允許在不改變已有代碼的情況下增加新的功能。下面的例子存在誤導性,僅作為參考。
**差:**
```php
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
}
}
```
**優:**
```php
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);
}
}
```
### 里氏替換原則 Liskov Substitution Principle (LSP)
這是一個簡單的原則,卻用了一個不好理解的術語。它的正式定義是
"如果S是T的子類型,那么在不改變程序原有既定屬性(檢查、執行
任務等)的前提下,任何T類型的對象都可以使用S類型的對象替代
(例如,使用S的對象可以替代T的對象)" 這個定義更難理解:-)。
對這個概念最好的解釋是:如果你有一個父類和一個子類,在不改變
原有結果正確性的前提下父類和子類可以互換。這個聽起來依舊讓人
有些迷惑,所以讓我們來看一個經典的正方形-長方形的例子。從數學
上講,正方形是一種長方形,但是當你的模型通過繼承使用了"is-a"
的關系時,就不對了。
**差:**
```php
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);
}
```
**優:**
最好是將這兩種四邊形分別對待,用一個適合兩種類型的更通用子類型來代替。
盡管正方形和長方形看起來很相似,但他們是不同的。
正方形更接近菱形,而長方形更接近平行四邊形。但他們不是子類型。
盡管相似,正方形、長方形、菱形、平行四邊形都是有自己屬性的不同形狀。
```php
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);
}
```
### 接口隔離原則 Interface Segregation Principle (ISP)
通用的接口往往會無意識的將自己和類的實現耦合在了一起,所以你應當盡量的避免這種情況的發生。在設計接口時,你應當時刻提醒自己,我是否需要使用所有在接口中聲明的方法呢?如果不是的話,將接口細分為更多個更精簡、更具體的接口。
接口隔離原則表示:"調用方不應該被強制依賴于他不需要的接口"
有一個清晰的例子來說明示范這條原則。當一個類需要一個大量的設置項,
為了方便不會要求調用方去設置大量的選項,因為在通常他們不需要所有的
設置項。使設置項可選有助于我們避免產生"胖接口"
**差:**
```php
interface Employee
{
public function work(): void;
public function eat(): void;
}
class Human implements Employee
{
public function work(): void
{
// ....工作
}
public function eat(): void
{
// ...... 吃飯休息
}
}
class Robot implements Employee
{
public function work(): void
{
//....工作
}
public function eat(): void
{
//.... 機器是沒有吃飯休息
}
}
```
**優:**
不是每一個工人都是雇員,但是每一個雇員都是一個工人
```php
interface Workable
{
public function work(): void;
}
interface Feedable
{
public function eat(): void;
}
interface Employee extends Feedable, Workable
{
}
class Human implements Employee
{
public function work(): void
{
// ....工作
}
public function eat(): void
{
//....吃飯休息
}
}
// robot can only work
class Robot implements Workable
{
public function work(): void
{
// ....工作
}
}
```
### 依賴反轉原則 Dependency Inversion Principle (DIP)
這條原則說明兩個基本的要點:
1. 高階的模塊不應該依賴低階的模塊,它們都應該依賴于抽象
2. 抽象不應該依賴于實現,實現應該依賴于抽象
這條起初看起來有點晦澀難懂,但是如果你使用過php框架(例如 Symfony),你應該見過依賴注入(DI)對這個概念的實現。雖然它們不是完全相通的概念,依賴倒置原則使高階模塊與低階模塊的實現細節和創建分離。可以使用依賴注入(DI)這種方式來實現它。更多的好處是它使模塊之間解耦。
**差:**
```php
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();
}
}
```
**優:**
```php
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();
}
}
```