類的SOLID原則 SOLID
1. S: 單一職責(zé)原則 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命名的最重要的五個(gè)面對對象編碼設(shè)計(jì)原則
S: 單一職責(zé)原則 (SRP)
O: 開閉原則 (OCP)
L: 里氏替換原則 (LSP)
I: 接口隔離原則 (ISP)
D: 依賴倒置原則 (DIP)
1. 單一職責(zé)原則
Single Responsibility Principle (SRP)
正如在Clean Code所述,"修改一個(gè)類應(yīng)該只為一個(gè)理由"。 人們總是易于用一堆方法塞滿一個(gè)類,如同我們只能在飛機(jī)上 只能攜帶一個(gè)行李箱(把所有的東西都塞到箱子里)。這樣做 的問題是:從概念上這樣的類不是高內(nèi)聚的,并且留下了很多 理由去修改它。將你需要修改類的次數(shù)降低到最小很重要。 這是因?yàn)?,?dāng)有很多方法在類中時(shí),修改其中一處,你很難知 曉在代碼庫中哪些依賴的模塊會(huì)被影響到。
壞:
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
等) 應(yīng)該對擴(kuò)展開放,對修改關(guān)閉。" 然而這句話意味著什么呢?這個(gè)原則大體上表示你 應(yīng)該允許在不改變已有代碼的情況下增加新的功能
壞:
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)
這是一個(gè)簡單的原則,卻用了一個(gè)不好理解的術(shù)語。它的正式定義是 "如果S是T的子類型,那么在不改變程序原有既定屬性(檢查、執(zhí)行 任務(wù)等)的前提下,任何T類型的對象都可以使用S類型的對象替代 (例如,使用S的對象可以替代T的對象)" 這個(gè)定義更難理解:-)。
對這個(gè)概念最好的解釋是:如果你有一個(gè)父類和一個(gè)子類,在不改變 原有結(jié)果正確性的前提下父類和子類可以互換。這個(gè)聽起來依舊讓人 有些迷惑,所以讓我們來看一個(gè)經(jīng)典的正方形-長方形的例子。從數(shù)學(xué) 上講,正方形是一種長方形,但是當(dāng)你的模型通過繼承使用了"is-a" 的關(guān)系時(shí),就不對了。
壞:
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); }
好:
最好是將這兩種四邊形分別對待,用一個(gè)適合兩種類型的更通用子類型來代替。
盡管正方形和長方形看起來很相似,但他們是不同的。 正方形更接近菱形,而長方形更接近平行四邊形。但他們不是子類型。 盡管相似,正方形、長方形、菱形、平行四邊形都是有自己屬性的不同形狀。
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)
接口隔離原則表示:"調(diào)用方不應(yīng)該被強(qiáng)制依賴于他不需要的接口"
有一個(gè)清晰的例子來說明示范這條原則。當(dāng)一個(gè)類需要一個(gè)大量的設(shè)置項(xiàng), 為了方便不會(huì)要求調(diào)用方去設(shè)置大量的選項(xiàng),因?yàn)樵谕ǔK麄儾恍枰械?設(shè)置項(xiàng)。使設(shè)置項(xiàng)可選有助于我們避免產(chǎn)生"胖接口"
壞:
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 } }
好:
不是每一個(gè)工人都是雇員,但是每一個(gè)雇員都是一個(gè)工人
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)
這條原則說明兩個(gè)基本的要點(diǎn):
高階的模塊不應(yīng)該依賴低階的模塊,它們都應(yīng)該依賴于抽象
抽象不應(yīng)該依賴于實(shí)現(xiàn),實(shí)現(xiàn)應(yīng)該依賴于抽象
這條起初看起來有點(diǎn)晦澀難懂,但是如果你使用過 PHP 框架(例如 Symfony),你應(yīng)該見過 依賴注入(DI),它是對這個(gè)概念的實(shí)現(xiàn)。雖然它們不是完全相等的概念,依賴倒置原則使高階模塊 與低階模塊的實(shí)現(xiàn)細(xì)節(jié)和創(chuàng)建分離??梢允褂靡蕾囎⑷耄―I)這種方式來實(shí)現(xiàn)它。最大的好處 是它使模塊之間解耦。耦合會(huì)導(dǎo)致你難于重構(gòu),它是一種非常糟糕的的開發(fā)模式。
壞:
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(); } }