前言
前幾天在GitHub看到一篇寫PHP簡潔之道的譯文,覺得還不錯,所以轉在了自己的博客中,只不過有一些地方好像沒有翻譯,再加上排版上的一些小問題,所以決定自己翻譯一遍。
原文地址:github.com/jupeter/cle…
變量
- 使用更有意義和更加直白的命名方式
不友好的:
$ymdstr = $moment->format('y-m-d');友好的:
$currentDate = $moment->format('y-m-d');
- 對于同一實體使用相同變量名
不友好的:
getUserInfo(); getUserData(); getUserRecord(); getUserProfile();友好的:
getUser();
- 使用可以查找到的變量
我們讀的代碼量遠比我們寫過的多。因此,寫出可閱讀和便于搜索的代碼是及其重要的。在我們的程序中寫出一些難以理解的變量名
到最后甚至會讓自己非常傷腦筋。
因此,讓你的名字便于搜索吧。不友好的:
// 這里的448代表什么意思呢? $result = $serializer->serialize($data, 448);友好的:
$json = $serializer->serialize($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | >JSON_UNESCAPED_UNICODE);
- 使用解釋型變量
不友好的
$address = 'One Infinite Loop, Cupertino 95014'; $cityZipCodeRegex = '/^[^,]+,\s*(.+?)\s*(\d{5})$/'; preg_match($cityZipCodeRegex, $address, $matches); saveCityZipCode($matches[1], $matches[2]);不至于那么糟糕的:
稍微好一些,但是這取決于我們對正則的熟練程度。
$address = 'One Infinite Loop, Cupertino 95014'; $cityZipCodeRegex = '/^[^,]+,\s*(.+?)\s*(\d{5})$/'; preg_match($cityZipCodeRegex, $address, $matches); [, $city, $zipCode] = $matches; saveCityZipCode($city, $zipCode);友好的:
通過對子模式的重命名減少了我們對正則的熟悉和依賴程度。
$address = 'One Infinite Loop, Cupertino 95014'; $cityZipCodeRegex = '/^[^,]+,\s*(?<city>.+?)\s*(?<zipCode>\d{5})$/'; preg_match($cityZipCodeRegex, $address, $matches); saveCityZipCode($matches['city'], $matches['zipCode']);
- 嵌套無邊,回頭是岸
太多的
if else嵌套會讓你的代碼難以閱讀和維護。更加直白的代碼會好很多。
- demo1
不友好的:
function isShopOpen($day): bool { if ($day) { if (is_string($day)) { $day = strtolower($day); if ($day === 'friday') { return true; } elseif ($day === 'saturday') { return true; } elseif ($day === 'sunday') { return true; } else { return false; } } else { return false; } } else { return false; } }友好的:
function isShopOpen(string $day): bool { if (empty($day)) { return false; } $openingDays = [ 'friday', 'saturday', 'sunday' ]; return in_array(strtolower($day), $openingDays, true); }
- demo2
不友好的:
function fibonacci(int $n) { if ($n < 50) { if ($n !== 0) { if ($n !== 1) { return fibonacci($n - 1) + fibonacci($n - 2); } else { return 1; } } else { return 0; } } else { return 'Not supported'; } }友好的:
function fibonacci(int $n): int { if ($n === 0 || $n === 1) { return $n; } if ($n > 50) { throw new \Exception('Not supported'); } return fibonacci($n - 1) + fibonacci($n - 2); }
- 避免使用不合理的變量名
別讓其他人去猜你的變量名的意思。
更加直白的代碼會好很多。不友好的:
$l = ['Austin', 'New York', 'San Francisco']; for ($i = 0; $i < count($l); $i++) { $li = $l[$i]; doStuff(); doSomeOtherStuff(); // ... // ... // ... // 等等,這個$li是什么意思? dispatch($li); }友好的:
$locations = ['Austin', 'New York', 'San Francisco']; foreach ($locations as $location) { doStuff(); doSomeOtherStuff(); // ... // ... // ... dispatch($location); }別添加沒必要的上下文
如果你的類或對象的名字已經傳達了一些信息,那么請別在變量名中重復。
不友好的
class Car { public $carMake; public $carModel; public $carColor; //... }友好的
class Car { public $make; public $model; public $color; //... }
- 用參數默認值代替短路運算或條件運算
不友好的
這里不太合理,因為變量
$breweryName有可能是NULL。function createMicrobrewery($breweryName = 'Hipster Brew Co.'): void { // ... }不至于那么糟糕的
這種寫法要比上一版稍微好理解一些,但是如果能控制變量值獲取會更好。
function createMicrobrewery($name = null): void { $breweryName = $name ?: 'Hipster Brew Co.'; // ... }友好的
如果你僅支持
PHP 7+,那么你可以使用類型約束并且保證$http://php.net/manual/en/functions.arguments.php#functions.arguments.type-declaration變量不會為NULL。function createMicrobrewery(string $breweryName = 'Hipster Brew Co.'): void { // ... }
函數
- 函數參數應該控制在兩個以下
限制函數的參數對于在對函數做測試來說相當重要。有超過三個可選的參數會給你的測試工作量帶來倍速增長。
最理想的情況是沒有參數。1-2個參數也還湊合,但是三個參數就應該避免了。參數越多,我們需要維護的就越多。通常,如果你的函>數有超過2個的參數,那么你的這個函數需要處理的事情就太多了。如果的確需要這么多參數,那么在大多數情況下, 用一個對象來處理可能會更合適。
不友好的:
function createMenu(string $title, string $body, string $buttonText, bool $cancellable): void { // ... }友好的:
class MenuConfig { public $title; public $body; public $buttonText; public $cancellable = false; } $config = new MenuConfig(); $config->title = 'Foo'; $config->body = 'Bar'; $config->buttonText = 'Baz'; $config->cancellable = true; function createMenu(MenuConfig $config): void { // ... }
- 一個函數只做一件事情
在軟件工程行業(yè),這是最重要的準則。當函數所處理的事情超過一件,他就會變得難以實現(xiàn),測試和理解。當你能讓一個函數僅僅負責一個事情,他們就會變得容易重構并且理解起來越清晰。光是執(zhí)行這樣一條原則就能讓你成為開發(fā)者中的佼佼者了。
不友好的:
function emailClients(array $clients): void { foreach ($clients as $client) { $clientRecord = $db->find($client); if ($clientRecord->isActive()) { email($client); } } }友好的:
function emailClients(array $clients): void { $activeClients = activeClients($clients); array_walk($activeClients, 'email'); } function activeClients(array $clients): array { return array_filter($clients, 'isClientActive'); } function isClientActive(int $client): bool { $clientRecord = $db->find($client); return $clientRecord->isActive(); }
- 函數名應該說到做到
不友好的:
class Email { //... public function handle(): void { mail($this->to, $this->subject, $this->body); } } $message = new Email(...); // 這是什么?這個`handle`方法是什么?我們現(xiàn)在應該寫入到一個文件嗎? $message->handle();友好的:
class Email { //... public function send(): void { mail($this->to, $this->subject, $this->body); } } $message = new Email(...); // 清晰并且顯而易見 $message->send();
- 函數應該只有一層抽象
當你的函數有超過一層的抽象時便意味著這個函數做了太多事情。解耦這個函數致使其變得可重用和更易測試。
不友好的:
function parseBetterJSAlternative(string $code): void { $regexes = [ // ... ]; $statements = explode(' ', $code); $tokens = []; foreach ($regexes as $regex) { foreach ($statements as $statement) { // ... } } $ast = []; foreach ($tokens as $token) { // lex... } foreach ($ast as $node) { // parse... } }同樣不太友好:
我們已經從函數中拆分除了一些東西出來,但是
parseBetterJSAlternative()這個函數還是太復雜以至于難以測試。function tokenize(string $code): array { $regexes = [ // ... ]; $statements = explode(' ', $code); $tokens = []; foreach ($regexes as $regex) { foreach ($statements as $statement) { $tokens[] = /* ... */; } } return $tokens; } function lexer(array $tokens): array { $ast = []; foreach ($tokens as $token) { $ast[] = /* ... */; } return $ast; } function parseBetterJSAlternative(string $code): void { $tokens = tokenize($code); $ast = lexer($tokens); foreach ($ast as $node) { // parse... } }友好的
最優(yōu)解就是把
parseBetterJSAlternative()函數依賴的東西分離出來。class Tokenizer { public function tokenize(string $code): array { $regexes = [ // ... ]; $statements = explode(' ', $code); $tokens = []; foreach ($regexes as $regex) { foreach ($statements as $statement) { $tokens[] = /* ... */; } } return $tokens; } } class Lexer { public function lexify(array $tokens): array { $ast = []; foreach ($tokens as $token) { $ast[] = /* ... */; } return $ast; } } class BetterJSAlternative { private $tokenizer; private $lexer; public function __construct(Tokenizer $tokenizer, Lexer $lexer) { $this->tokenizer = $tokenizer; $this->lexer = $lexer; } public function parse(string $code): void { $tokens = $this->tokenizer->tokenize($code); $ast = $this->lexer->lexify($tokens); foreach ($ast as $node) { // parse... } } }
- 不要在函數中帶入
flag相關的參數
當你使用
flag時便意味著你的函數做了超過一件事情。前面我們也提到了,函數應該只做一件事情。如果你的代碼取決于一個boolean,那么還是把這些內容拆分出來吧。不友好的
function createFile(string $name, bool $temp = false): void { if ($temp) { touch('./temp/'.$name); } else { touch($name); } }友好的
function createFile(string $name): void { touch($name); } function createTempFile(string $name): void { touch('./temp/'.$name); }
- 避免函數帶來的副作用
當函數有數據的輸入和輸出時可能會產生副作用。這個副作用可能被寫入一個文件,修改一些全局變量,或者意外的把你的錢轉給一個陌生人。
此刻,你可能有時候會需要這些副作用。正如前面所說,你可能需要寫入到一個文件中。你需要注意的是把這些你所做的東西在你的掌控之下。別讓某些個別函數或者類寫入了一個特別的文件。對于所有的都應該一視同仁。有且僅有一個結果。
重要的是要避免那些譬如共享無結構的對象,使用可以寫入任何類型的可變數據,不對副作用進行集中處理等常見的陷阱。如果你可以做到,你將會比大多數程序猿更加輕松。
不友好的
// 全局變量被下面的函數引用了。 // 如果我們在另外一個函數中使用了這個`$name`變量,那么可能會變成一個數組或者程序被打斷。 $name = 'Ryan McDermott'; function splitIntoFirstAndLastName(): void { global $name; $name = explode(' ', $name); } splitIntoFirstAndLastName(); var_dump($name); // ['Ryan', 'McDermott'];友好的
function splitIntoFirstAndLastName(string $name): array { return explode(' ', $name); } $name = 'Ryan McDermott'; $newName = splitIntoFirstAndLastName($name); var_dump($name); // 'Ryan McDermott'; var_dump($newName); // ['Ryan', 'McDermott'];
- 避免寫全局方法
在大多數語言中,全局變量被污染都是一個不太好的實踐,因為當你引入另外的包時會起沖突并且使用你的
API的人知道拋出了一個異常才明白。我們假設一個簡單的例子:如果你想要一個配置數組。你可能會寫一個類似于config()的全局的函數,但是在引入其他包并在其他地方嘗試做同樣的事情時會起沖突。不友好的
function config(): array { return [ 'foo' => 'bar', ] }不友好的
class Configuration { private $configuration = []; public function __construct(array $configuration) { $this->configuration = $configuration; } public function get(string $key): ?string { return isset($this->configuration[$key]) ? $this->configuration[$key] : null; } }通過創(chuàng)建
Configuration類的實例來引入配置$configuration = new Configuration([ 'foo' => 'bar', ]);至此,你就可以是在你的項目中使用這個配置了。
- 避免使用單例模式
單例模式是一種反模式。為什么不建議使用:
- 他們通常使用一個全局實例,為什么這么糟糕?因為你隱藏了依賴關系在你的項目的代碼中,而不是通過接口暴露出來。你應該有意識的去避免那些全局的東西。
- 他們違背了單一職責原則:他們會自己控制自己的生命周期。
- 這種模式會自然而然的使代碼耦合在一起。這會讓他們在測試中,很多情況下都理所當然的不一致。
- 他們持續(xù)在整個項目的生命周期中。另外一個嚴重的打擊是當你需要排序測試的時候,在單元測試中這會是一個不小的麻煩。為什么?因為每個單元測試都應該依賴于另外一個。
不友好的
class DBConnection { private static $instance; private function __construct(string $dsn) { // ... } public static function getInstance(): DBConnection { if (self::$instance === null) { self::$instance = new self(); } return self::$instance; } // ... } $singleton = DBConnection::getInstance();友好的
class DBConnection { public function __construct(string $dsn) { // ... } // ... }使用DSN配置來創(chuàng)建一個
DBConnection類的單例。$connection = new DBConnection($dsn);此時,在你的項目中必須使用
DBConnection的單例。
- 對條件判斷進行包裝
不友好的
if ($article->state === 'published') { // ... }友好的
if ($article->isPublished()) { // ... }
- 避免對條件取反
不友好的
function isDOMNodeNotPresent(\DOMNode $node): bool { // ... } if (!isDOMNodeNotPresent($node)) { // ... }友好的
function isDOMNodePresent(\DOMNode $node): bool { // ... } if (isDOMNodePresent($node)) { // ... }
- 避免太多的條件嵌套
這似乎是一個不可能的任務。很多人的腦海中可能會在第一時間縈繞“如果沒有
if條件我還能做什么呢?”。答案就是,在大多數情況下,你可以使用多態(tài)去處理這個難題。此外,可能有人又會說了,“即使多態(tài)可以做到,但是我們?yōu)槭裁匆@么做呢?”,對此我們的解釋是,一個函數應該只做一件事情,這也正是我們在前面所提到的讓代碼更加整潔的原則。當你的函數中使用了太多的if條件時,便意味著你的函數做了超過一件事情。牢記:要專一。不友好的:
class Airplane { // ... public function getCruisingAltitude(): int { switch ($this->type) { case '777': return $this->getMaxAltitude() - $this->getPassengerCount(); case 'Air Force One': return $this->getMaxAltitude(); case 'Cessna': return $this->getMaxAltitude() - $this->getFuelExpenditure(); } } }友好的:
interface Airplane { // ... public function getCruisingAltitude(): int; } class Boeing777 implements Airplane { // ... public function getCruisingAltitude(): int { return $this->getMaxAltitude() - $this->getPassengerCount(); } } class AirForceOne implements Airplane { // ... public function getCruisingAltitude(): int { return $this->getMaxAltitude(); } } class Cessna implements Airplane { // ... public function getCruisingAltitude(): int { return $this->getMaxAltitude() - $this->getFuelExpenditure(); } }
- 避免類型檢測 (part 1)
PHP是一門弱類型語言,這意味著你的函數可以使用任何類型的參數。他在給予你無限的自由的同時又讓你困擾,因為有有時候你需要做類型檢測。這里有很多方式去避免這種事情,第一種方式就是統(tǒng)一API。不友好的:
function travelToTexas($vehicle): void { if ($vehicle instanceof Bicycle) { $vehicle->pedalTo(new Location('texas')); } elseif ($vehicle instanceof Car) { $vehicle->driveTo(new Location('texas')); } }友好的:
function travelToTexas(Traveler $vehicle): void { $vehicle->travelTo(new Location('texas')); }
- 避免類型檢測 (part 2)
如果你正使用諸如字符串、整型和數組等基本類型,且要求版本是PHP 7+,不能使用多態(tài),需要類型檢測,那你應當考慮類型聲明或者嚴格模式。它提供了基于標準PHP語法的靜態(tài)類型。手動檢查類型的問題是做好了需要好多的廢話,好像為了安全就可以不顧損失可讀性。保持你的
PHP代碼整潔,寫好測試,保持良好的回顧代碼的習慣。否則的話,那就還是用PHP嚴格類型聲明和嚴格模式來確保安全吧。不友好的:
function combine($val1, $val2): int { if (!is_numeric($val1) || !is_numeric($val2)) { throw new \Exception('Must be of type Number'); } return $val1 + $val2; }友好的:
function combine(int $val1, int $val2): int { return $val1 + $val2; }
- 移除那些沒有使用的代碼
沒有再使用的代碼就好比重復代碼一樣糟糕。在你的代碼庫中完全沒有必要保留。如果確定不再使用,那就把它刪掉吧!如果有一天你要使用,你也可以在你的版本記錄中找到它。
不友好的:
function oldRequestModule(string $url): void { // ... } function newRequestModule(string $url): void { // ... } $request = newRequestModule($requestUrl); inventoryTracker('apples', $request, 'www.inventory-awesome.io');友好的:
function requestModule(string $url): void { // ... } $request = requestModule($requestUrl); inventoryTracker('apples', $request, 'www.inventory-awesome.io');
對象和數據結構
- 使用對象封裝
在
PHP中你可以設置public,protected,和private關鍵詞來修飾你的方法。當你使用它們,你就可以在一個對象中控制這些屬性的修改權限了。
- 當你想要對對象的屬性進行除了“獲取”之外的操作時,你不必再去瀏覽并在代碼庫中修改權限。
- 當你要做一些修改屬性的操作時,你更易于在代碼中做邏輯驗證。
- 封裝內部表示。
- 當你在做獲取和設置屬性的操作時,更易于添加
log或error的操作。- 當其他
class繼承了這個基類,你可以重寫默認的方法。- 你可以為一個服務延遲的去獲取這個對象的屬性值。
不太友好的:
class BankAccount { public $balance = 1000; } $bankAccount = new BankAccount(); // Buy shoes... $bankAccount->balance -= 100;友好的:
class BankAccount { private $balance; public function __construct(int $balance = 1000) { $this->balance = $balance; } public function withdraw(int $amount): void { if ($amount > $this->balance) { throw new \Exception('Amount greater than available balance.'); } $this->balance -= $amount; } public function deposit(int $amount): void { $this->balance += $amount; } public function getBalance(): int { return $this->balance; } } $bankAccount = new BankAccount(); // Buy shoes... $bankAccount->withdraw($shoesPrice); // Get balance $balance = $bankAccount->getBalance();
- 在對象的屬性上可以使用
private/protected限定
public修飾的方法和屬性同上來說被修改是比較危險的,因為一些外部的代碼可以輕易的依賴于他們并且你沒辦法控制哪些代碼依賴于他們。對于所有用戶的類來說,在類中可以修改是相當危險的。protected修飾器和public同樣危險,因為他們在繼承鏈中同樣可以操作。二者的區(qū)別僅限于權限機制,并且封裝保持不變。對于所有子類來說,在類中修改也是相當危險的。private修飾符保證了代碼只有在自己類的內部修改才是危險的。因此,當你在需要對外部的類設置權限時使用
private修飾符去取代public/protected吧。如果需要了解更多信息你可以讀Fabien Potencier寫的這篇文章
不太友好的:
class Employee { public $name; public function __construct(string $name) { $this->name = $name; } } $employee = new Employee('John Doe'); echo 'Employee name: '.$employee->name; // Employee name: John Doe友好的:
class Employee { private $name; public function __construct(string $name) { $this->name = $name; } public function getName(): string { return $this->name; } } $employee = new Employee('John Doe'); echo 'Employee name: '.$employee->getName(); // Employee name: John Doe
- 組合優(yōu)于繼承
正如
the Gang of Four在著名的Design Patterns中所說,你應該盡可能的使用組合而不是繼承。不管是使用組合還是繼承都有很多的優(yōu)點。最重要的一個準則在于當你本能的想要使用繼承時,不妨思考一下組合是否能讓你的問題解決的更加優(yōu)雅。在某些時候確實如此。你可能會這么問了,“那到底什么時候我應該使用繼承呢?”這完全取決你你手頭上的問題,下面正好有一些繼承優(yōu)于組合的例子:
- 你的繼承表達了“是一個”而不是“有一個”的關系(Human->Animal vs. User->UserDetails)。
- 你可能會重復的使用基類的代碼(Humans can move like all animals)。
- 你渴望在修改代碼的時候通過基類來統(tǒng)一調度(Change the caloric expenditure of all animals when they move)。
不友好的:
class Employee { private $name; private $email; public function __construct(string $name, string $email) { $this->name = $name; $this->email = $email; } // ... } // 這里不太合理的原因在于并非所有的職員都有`tax`這個特征。 class EmployeeTaxData extends Employee { private $ssn; private $salary; public function __construct(string $name, string $email, string $ssn, string $salary) { parent::__construct($name, $email); $this->ssn = $ssn; $this->salary = $salary; } // ... }友好的:
class EmployeeTaxData { private $ssn; private $salary; public function __construct(string $ssn, string $salary) { $this->ssn = $ssn; $this->salary = $salary; } // ... } class Employee { private $name; private $email; private $taxData; public function __construct(string $name, string $email) { $this->name = $name; $this->email = $email; } public function setTaxData(string $ssn, string $salary) { $this->taxData = new EmployeeTaxData($ssn, $salary); } // ... }
- 避免鏈式調用(連貫接口)
在使用一些鏈式方法時,這種連貫接口可以不斷地指向當前對象讓我們的代碼顯得更加清晰可讀。
通常情況下,我們在構建對象時都可以利用他的上下文這一特征,因為這種模式可以減少代碼的冗余,不過在PHPUnit Mock Builder或者Doctrine Query Builder所提及的,有時候這種方式會帶來一些麻煩:
如果需要了解更多信息你可以讀Marco Pivetta寫的這篇文章
友好的:
class Car { private $make = 'Honda'; private $model = 'Accord'; private $color = 'white'; public function setMake(string $make): self { $this->make = $make; // NOTE: Returning this for chaining return $this; } public function setModel(string $model): self { $this->model = $model; // NOTE: Returning this for chaining return $this; } public function setColor(string $color): self { $this->color = $color; // NOTE: Returning this for chaining return $this; } public function dump(): void { var_dump($this->make, $this->model, $this->color); } } $car = (new Car()) ->setColor('pink') ->setMake('Ford') ->setModel('F-150') ->dump();不友好的:
class Car { private $make = 'Honda'; private $model = 'Accord'; private $color = 'white'; public function setMake(string $make): void { $this->make = $make; } public function setModel(string $model): void { $this->model = $model; } public function setColor(string $color): void { $this->color = $color; } public function dump(): void { var_dump($this->make, $this->model, $this->color); } } $car = new Car(); $car->setColor('pink'); $car->setMake('Ford'); $car->setModel('F-150'); $car->dump();
SOLID
SOLID最開始是由Robert Martin提出的五個準則,并最后由Michael Feathers命名的簡寫,這五個是在面對對象設計中的五個基本原則。
- S: 職責單一原則 (SRP)
- O: 開閉原則 (OCP)
- L: 里氏替換原則 (LSP)
- I: 接口隔離原則 (ISP)
- D: 依賴反轉原則 (DIP)
- 職責單一原則 (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()) { // ... } } }
- 開閉原則 (OCP)
正如Bertrand Meyer所說,“軟件開發(fā)應該對擴展開發(fā),對修改關閉?!边@是什么意思呢?這個原則的意思大概就是說你應該允許其他人在不修改已經存在的功能的情況下去增加新功能。
不友好的
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); } }
- 里氏替換原則 (LSP)
這本身是一個非常簡單的原則卻起了一個不太容易理解的名字。這個原則通常的定義是“如果S是T的一個子類,那么對象T可以在沒有任何警告的情況下被他的子類替換(例如:對象S可能代替對象T)一些更合適的屬性?!焙孟窀y理解了。
最好的解釋就是說如果你有一個父類和子類,那么你的父類和子類可以在原來的基礎上任意交換。這個可能還是難以理解,我們舉一個正方形-長方形的例子吧。在數學中,一個矩形屬于長方形,但是如果在你的模型中通過繼承使用了“is-a”的關系就不對了。
不友好的:
class Rectangle { protected $width = 0; protected $height = 0; public function render(int $area): void { // ... } 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 renderLargeRectangles(array $rectangles): void { foreach ($rectangles as $rectangle) { $rectangle->setWidth(4); $rectangle->setHeight(5); $area = $rectangle->getArea(); // BAD: Will return 25 for Square. Should be 20. $rectangle->render($area); } } $rectangles = [new Rectangle(), new Rectangle(), new Square()]; renderLargeRectangles($rectangles);友好的:
abstract class Shape { protected $width = 0; protected $height = 0; abstract public function getArea(): int; public function render(int $area): void { // ... } } class Rectangle extends Shape { 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 Shape { private $length = 0; public function setLength(int $length): void { $this->length = $length; } public function getArea(): int { return pow($this->length, 2); } } function renderLargeRectangles(array $rectangles): void { foreach ($rectangles as $rectangle) { if ($rectangle instanceof Square) { $rectangle->setLength(5); } elseif ($rectangle instanceof Rectangle) { $rectangle->setWidth(4); $rectangle->setHeight(5); } $area = $rectangle->getArea(); $rectangle->render($area); } } $shapes = [new Rectangle(), new Rectangle(), new Square()]; renderLargeRectangles($shapes);
- 接口隔離原則 (ISP)
ISP的意思就是說“使用者不應該強制使用它不需要的接口”。
當一個類需要大量的設置是一個不錯的例子去解釋這個原則。為了方便去調用這個接口需要做大量的設置,但是大多數情況下是不需要的。強制讓他們使用這些設置會讓整個接口顯得臃腫。
不友好的:
interface Employee { public function work(): void; public function eat(): void; } class Human implements Employee { public function work(): void { // ....working } public function eat(): void { // ...... eating in lunch break } } class Robot 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 Human implements Employee { public function work(): void { // ....working } public function eat(): void { //.... eating in lunch break } } // robot can only work class Robot implements Workable { public function work(): void { // ....working } }
- 依賴反轉原則 (DIP)
這個原則有兩個需要注意的地方:
- 高階模塊不能依賴于低階模塊。他們都應該依賴于抽象。
- 抽象不應該依賴于實現(xiàn),實現(xiàn)應該依賴于抽象。
第一點可能有點難以理解,但是如果你有使用過像
Symfony的PHP框架,你應該有見到過依賴注入這樣的原則的實現(xiàn)。盡管他們是不一樣的概念,DIP讓高階模塊從我們所知道的低階模塊中分離出去??梢酝ㄟ^DI這種方式實現(xiàn)。一個巨大的好處在于它解耦了不同的模塊。耦合是一個非常不好的開發(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(); } }
別重復你的代碼 (DRY)
嘗試去研究DRY原則。
盡可能別去復制代碼。復制代碼非常不好,因為這意味著將來有需要修改的業(yè)務邏輯時你需要修改不止一處。
想象一下你在經營一個餐館并且你需要經常整理你的存貨清單:你所有的土豆,洋蔥,大蒜,辣椒等。如果你有多個列表來管理進銷記錄,當你用其中一些土豆做菜時你需要更新所有的列表。如果你只有一個列表的話只有一個地方需要更新!
大多數情況下你有重復的代碼是因為你有超過兩處細微的差別,他們大部分都是相同的,但是他們的不同之處又不得不讓你去分成不同的方法去處理相同的事情。移除這些重復的代碼意味著你需要創(chuàng)建一個可以用一個方法/模塊/類來處理的抽象。
使用一個抽象是關鍵的,這也是為什么在類中你要遵循
SOLID原則的原因。一個不優(yōu)雅的抽象往往比重復的代碼更糟糕,所以要謹慎使用!說了這么多,如果你已經可以構造一個優(yōu)雅的抽象,那就趕緊去做吧!別重復你的代碼,否則當你需要修改時你會發(fā)現(xiàn)你要修改許多地方。不友好的:
function showDeveloperList(array $developers): void { foreach ($developers as $developer) { $expectedSalary = $developer->calculateExpectedSalary(); $experience = $developer->getExperience(); $githubLink = $developer->getGithubLink(); $data = [ $expectedSalary, $experience, $githubLink ]; render($data); } } function showManagerList(array $managers): void { foreach ($managers as $manager) { $expectedSalary = $manager->calculateExpectedSalary(); $experience = $manager->getExperience(); $githubLink = $manager->getGithubLink(); $data = [ $expectedSalary, $experience, $githubLink ]; render($data); } }友好的:
function showList(array $employees): void { foreach ($employees as $employee) { $expectedSalary = $employee->calculateExpectedSalary(); $experience = $employee->getExperience(); $githubLink = $employee->getGithubLink(); $data = [ $expectedSalary, $experience, $githubLink ]; render($data); } }非常優(yōu)雅的:
如果能更簡潔那就更好了。
function showList(array $employees): void { foreach ($employees as $employee) { render([ $employee->calculateExpectedSalary(), $employee->getExperience(), $employee->getGithubLink() ]); } }