# Clean Code PHP
牛永光推薦:[https://www.jianshu.com/p/6439e94b28b9](https://www.jianshu.com/p/6439e94b28b9)
## 目錄
1. [介紹](#%E4%BB%8B%E7%BB%8D)
2. [變量](#%E5%8F%98%E9%87%8F)
* [使用見字知意的變量名](#%E4%BD%BF%E7%94%A8%E8%A7%81%E5%AD%97%E7%9F%A5%E6%84%8F%E7%9A%84%E5%8F%98%E9%87%8F%E5%90%8D)
* [同一個實體要用相同的變量名](#%E5%90%8C%E4%B8%80%E4%B8%AA%E5%AE%9E%E4%BD%93%E8%A6%81%E7%94%A8%E7%9B%B8%E5%90%8C%E7%9A%84%E5%8F%98%E9%87%8F%E5%90%8D)
* [使用便于搜索的名稱 (part 1)](#%E4%BD%BF%E7%94%A8%E4%BE%BF%E4%BA%8E%E6%90%9C%E7%B4%A2%E7%9A%84%E5%90%8D%E7%A7%B0-part-1)
* [使用便于搜索的名稱 (part 2)](#%E4%BD%BF%E7%94%A8%E4%BE%BF%E4%BA%8E%E6%90%9C%E7%B4%A2%E7%9A%84%E5%90%8D%E7%A7%B0-part-2)
* [使用自解釋型變量](#%E4%BD%BF%E7%94%A8%E8%87%AA%E8%A7%A3%E9%87%8A%E5%9E%8B%E5%8F%98%E9%87%8F)
* [避免深層嵌套,盡早返回 (part 1)](#%E9%81%BF%E5%85%8D%E6%B7%B1%E5%B1%82%E5%B5%8C%E5%A5%97%E5%B0%BD%E6%97%A9%E8%BF%94%E5%9B%9E-part-1)
* [避免深層嵌套,盡早返回 (part 2)](#%E9%81%BF%E5%85%8D%E6%B7%B1%E5%B1%82%E5%B5%8C%E5%A5%97%E5%B0%BD%E6%97%A9%E8%BF%94%E5%9B%9E-part-2)
* [少用無意義的變量名](#%E5%B0%91%E7%94%A8%E6%97%A0%E6%84%8F%E4%B9%89%E7%9A%84%E5%8F%98%E9%87%8F%E5%90%8D)
* [不要添加不必要上下文](#%E4%B8%8D%E8%A6%81%E6%B7%BB%E5%8A%A0%E4%B8%8D%E5%BF%85%E8%A6%81%E4%B8%8A%E4%B8%8B%E6%96%87)
* [合理使用參數默認值,沒必要在方法里再做默認值檢測](#%E5%90%88%E7%90%86%E4%BD%BF%E7%94%A8%E5%8F%82%E6%95%B0%E9%BB%98%E8%AE%A4%E5%80%BC%E6%B2%A1%E5%BF%85%E8%A6%81%E5%9C%A8%E6%96%B9%E6%B3%95%E9%87%8C%E5%86%8D%E5%81%9A%E9%BB%98%E8%AE%A4%E5%80%BC%E6%A3%80%E6%B5%8B)
3. [表達式](#%E8%A1%A8%E8%BE%BE%E5%BC%8F)
* [使用恒等式](#%E4%BD%BF%E7%94%A8%E6%81%92%E7%AD%89%E5%BC%8F)
4. [函數](#%E5%87%BD%E6%95%B0)
* [函數參數(最好少于2個)](#%E5%87%BD%E6%95%B0%E5%8F%82%E6%95%B0-%E6%9C%80%E5%A5%BD%E5%B0%91%E4%BA%8E2%E4%B8%AA)
* [函數應該只做一件事](#%E5%87%BD%E6%95%B0%E5%BA%94%E8%AF%A5%E5%8F%AA%E5%81%9A%E4%B8%80%E4%BB%B6%E4%BA%8B)
* [函數名應體現他做了什么事](#%E5%87%BD%E6%95%B0%E5%90%8D%E5%BA%94%E4%BD%93%E7%8E%B0%E4%BB%96%E5%81%9A%E4%BA%86%E4%BB%80%E4%B9%88%E4%BA%8B)
* [函數里應當只有一層抽象abstraction](#%E5%87%BD%E6%95%B0%E9%87%8C%E5%BA%94%E5%BD%93%E5%8F%AA%E6%9C%89%E4%B8%80%E5%B1%82%E6%8A%BD%E8%B1%A1abstraction)
* [不要用flag作為函數的參數](#%E4%B8%8D%E8%A6%81%E7%94%A8flag%E4%BD%9C%E4%B8%BA%E5%87%BD%E6%95%B0%E7%9A%84%E5%8F%82%E6%95%B0)
* [避免副作用](#%E9%81%BF%E5%85%8D%E5%89%AF%E4%BD%9C%E7%94%A8)
* [不要寫全局函數](#%E4%B8%8D%E8%A6%81%E5%86%99%E5%85%A8%E5%B1%80%E5%87%BD%E6%95%B0)
* [不要使用單例模式](#%E4%B8%8D%E8%A6%81%E4%BD%BF%E7%94%A8%E5%8D%95%E4%BE%8B%E6%A8%A1%E5%BC%8F)
* [封裝條件語句](#%E5%B0%81%E8%A3%85%E6%9D%A1%E4%BB%B6%E8%AF%AD%E5%8F%A5)
* [避免用反義條件判斷](#%E9%81%BF%E5%85%8D%E7%94%A8%E5%8F%8D%E4%B9%89%E6%9D%A1%E4%BB%B6%E5%88%A4%E6%96%AD)
* [避免條件判斷](#%E9%81%BF%E5%85%8D%E6%9D%A1%E4%BB%B6%E5%88%A4%E6%96%AD)
* [避免類型檢查 (part 1)](#%E9%81%BF%E5%85%8D%E7%B1%BB%E5%9E%8B%E6%A3%80%E6%9F%A5-part-1)
* [避免類型檢查 (part 2)](#%E9%81%BF%E5%85%8D%E7%B1%BB%E5%9E%8B%E6%A3%80%E6%9F%A5-part-2)
* [移除僵尸代碼](#%E7%A7%BB%E9%99%A4%E5%83%B5%E5%B0%B8%E4%BB%A3%E7%A0%81)
5. [對象和數據結構 Objects and Data Structures](#%E5%AF%B9%E8%B1%A1%E5%92%8C%E6%95%B0%E6%8D%AE%E7%BB%93%E6%9E%84)
* [使用 getters 和 setters Use object encapsulation](#%E4%BD%BF%E7%94%A8-getters-%E5%92%8C-setters)
* [給對象使用私有或受保護的成員變量](#%E7%BB%99%E5%AF%B9%E8%B1%A1%E4%BD%BF%E7%94%A8%E7%A7%81%E6%9C%89%E6%88%96%E5%8F%97%E4%BF%9D%E6%8A%A4%E7%9A%84%E6%88%90%E5%91%98%E5%8F%98%E9%87%8F)
6. [類](#%E7%B1%BB)
* [少用繼承多用組合](#%E5%B0%91%E7%94%A8%E7%BB%A7%E6%89%BF%E5%A4%9A%E7%94%A8%E7%BB%84%E5%90%88)
* [避免連貫接口](#%E9%81%BF%E5%85%8D%E8%BF%9E%E8%B4%AF%E6%8E%A5%E5%8F%A3)
* [推薦使用 final 類](#%E6%8E%A8%E8%8D%90%E4%BD%BF%E7%94%A8-final-%E7%B1%BB)
7. [類的SOLID原則 SOLID](#solid)
* [S: 單一職責原則 Single Responsibility Principle (SRP)](#%E5%8D%95%E4%B8%80%E8%81%8C%E8%B4%A3%E5%8E%9F%E5%88%99)
* [O: 開閉原則 Open/Closed Principle (OCP)](#%E5%BC%80%E9%97%AD%E5%8E%9F%E5%88%99)
* [L: 里氏替換原則 Liskov Substitution Principle (LSP)](#%E9%87%8C%E6%B0%8F%E6%9B%BF%E6%8D%A2%E5%8E%9F%E5%88%99)
* [I: 接口隔離原則 Interface Segregation Principle (ISP)](#%E6%8E%A5%E5%8F%A3%E9%9A%94%E7%A6%BB%E5%8E%9F%E5%88%99)
* [D: 依賴倒置原則 Dependency Inversion Principle (DIP)](#%E4%BE%9D%E8%B5%96%E5%80%92%E7%BD%AE%E5%8E%9F%E5%88%99)
8. [別寫重復代碼 (DRY)](#%E5%88%AB%E5%86%99%E9%87%8D%E5%A4%8D%E4%BB%A3%E7%A0%81-dry)
9. [翻譯](#%E7%BF%BB%E8%AF%91)
## 介紹
本文參考自 Robert C. Martin的[*Clean Code*](https://links.jianshu.com/go?to=https%3A%2F%2Fwww.amazon.com%2FClean-Code-Handbook-Software-Craftsmanship%2Fdp%2F0132350882) 書中的軟件工程師的原則
,適用于PHP。 這不是風格指南。 這是一個關于開發可讀、可復用并且可重構的PHP軟件指南。
并不是這里所有的原則都得遵循,甚至很少的能被普遍接受。 這些雖然只是指導,但是都是*Clean Code*作者多年總結出來的。
本文受到 [clean-code-javascript](https://links.jianshu.com/go?to=https%3A%2F%2Fgithub.com%2Fryanmcdermott%2Fclean-code-javascript) 的啟發
雖然很多開發者還在使用PHP5,但是本文中的大部分示例的運行環境需要PHP 7.1+。
## 翻譯說明
翻譯完成度100%,最后更新時間2017-12-25。本文由 php-cpm 基于 [yangweijie版本](https://links.jianshu.com/go?to=https%3A%2F%2Fgithub.com%2Fyangweijie%2Fclean-code-php) 的[clean-code-php](https://links.jianshu.com/go?to=https%3A%2F%2Fgithub.com%2Fjupeter%2Fclean-code-php)翻譯并同步大量原文內容。
原文更新頻率較高,我的翻譯方法是直接用文本比較工具逐行對比。優先保證文字內容是最新的,再逐步提升翻譯質量。
閱讀過程中如果遇到各種鏈接失效、內容老舊、術語使用錯誤和其他翻譯錯誤等問題,歡迎大家積極提交PR。
## **變量**
### 使用見字知意的變量名
**壞:**
~~~php
$ymdstr = $moment->format('y-m-d');
~~~
**好:**
~~~php
$currentDate = $moment->format('y-m-d');
~~~
**[? 返回頂部](#%E7%9B%AE%E5%BD%95)**
### 同一個實體要用相同的變量名
**壞:**
~~~php
getUserInfo();
getUserData();
getUserRecord();
getUserProfile();
~~~
**好:**
~~~php
getUser();
~~~
**[? 返回頂部](#%E7%9B%AE%E5%BD%95)**
### 使用便于搜索的名稱 (part 1)
寫代碼是用來讀的。所以寫出可讀性高、便于搜索的代碼至關重要。
命名變量時如果沒有有意義、不好理解,那就是在傷害讀者。
請讓你的代碼便于搜索。
**壞:**
~~~php
// What the heck is 448 for?
$result = $serializer->serialize($data, 448);
~~~
**好:**
~~~php
$json = $serializer->serialize($data, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);
~~~
### 使用便于搜索的名稱 (part 2)
**壞:**
~~~php
// What the heck is 4 for?
if ($user->access & 4) {
// ...
}
~~~
**好:**
~~~php
class User
{
const ACCESS_READ = 1;
const ACCESS_CREATE = 2;
const ACCESS_UPDATE = 4;
const ACCESS_DELETE = 8;
}
if ($user->access & User::ACCESS_UPDATE) {
// do edit ...
}
~~~
**[? 返回頂部](#%E7%9B%AE%E5%BD%95)**
### 使用自解釋型變量
**壞:**
~~~php
$address = 'One Infinite Loop, Cupertino 95014';
$cityZipCodeRegex = '/^[^,]+,\s*(.+?)\s*(\d{5})$/';
preg_match($cityZipCodeRegex, $address, $matches);
saveCityZipCode($matches[1], $matches[2]);
~~~
**不錯:**
好一些,但強依賴于正則表達式的熟悉程度
~~~php
$address = 'One Infinite Loop, Cupertino 95014';
$cityZipCodeRegex = '/^[^,]+,\s*(.+?)\s*(\d{5})$/';
preg_match($cityZipCodeRegex, $address, $matches);
[, $city, $zipCode] = $matches;
saveCityZipCode($city, $zipCode);
~~~
**好:**
使用帶名字的子規則,不用懂正則也能看的懂
~~~php
$address = 'One Infinite Loop, Cupertino 95014';
$cityZipCodeRegex = '/^[^,]+,\s*(?<city>.+?)\s*(?<zipCode>\d{5})$/';
preg_match($cityZipCodeRegex, $address, $matches);
saveCityZipCode($matches['city'], $matches['zipCode']);
~~~
**[? 返回頂部](#%E7%9B%AE%E5%BD%95)**
### 避免深層嵌套,盡早返回 (part 1)
太多的if else語句通常會導致你的代碼難以閱讀,直白優于隱晦
**糟糕:**
~~~php
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;
}
}
~~~
**好:**
~~~php
function isShopOpen(string $day): bool
{
if (empty($day)) {
return false;
}
$openingDays = [
'friday', 'saturday', 'sunday'
];
return in_array(strtolower($day), $openingDays, true);
}
~~~
**[? 返回頂部](#%E7%9B%AE%E5%BD%95)**
### 避免深層嵌套,盡早返回 (part 2)
**糟糕的:**
~~~php
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';
}
}
~~~
**好:**
~~~php
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);
}
~~~
**[? 返回頂部](#%E7%9B%AE%E5%BD%95)**
### 少用無意義的變量名
別讓讀你的代碼的人猜你寫的變量是什么意思。
寫清楚好過模糊不清。
**壞:**
~~~php
$l = ['Austin', 'New York', 'San Francisco'];
for ($i = 0; $i < count($l); $i++) {
$li = $l[$i];
doStuff();
doSomeOtherStuff();
// ...
// ...
// ...
// 等等, `$li` 又代表什么?
dispatch($li);
}
~~~
**好:**
~~~php
$locations = ['Austin', 'New York', 'San Francisco'];
foreach ($locations as $location) {
doStuff();
doSomeOtherStuff();
// ...
// ...
// ...
dispatch($location);
}
~~~
**[? 返回頂部](#%E7%9B%AE%E5%BD%95)**
### 不要添加不必要上下文
如果從你的類名、對象名已經可以得知一些信息,就別再在變量名里重復。
**壞:**
~~~php
class Car
{
public $carMake;
public $carModel;
public $carColor;
//...
}
~~~
**好:**
~~~php
class Car
{
public $make;
public $model;
public $color;
//...
}
~~~
**[? 返回頂部](#%E7%9B%AE%E5%BD%95)**
### 合理使用參數默認值,沒必要在方法里再做默認值檢測
**不好:**
不好,`$breweryName` 可能為 `NULL`.
~~~php
function createMicrobrewery($breweryName = 'Hipster Brew Co.'): void
{
// ...
}
~~~
**還行:**
比上一個好理解一些,但最好能控制變量的值
~~~php
function createMicrobrewery($name = null): void
{
$breweryName = $name ?: 'Hipster Brew Co.';
// ...
}
~~~
**好:**
如果你的程序只支持 PHP 7+, 那你可以用 [type hinting](https://links.jianshu.com/go?to=http%3A%2F%2Fphp.net%2Fmanual%2Fen%2Ffunctions.arguments.php%23functions.arguments.type-declaration) 保證變量 `$breweryName` 不是 `NULL`.
~~~php
function createMicrobrewery(string $breweryName = 'Hipster Brew Co.'): void
{
// ...
}
~~~
**[? 返回頂部](#%E7%9B%AE%E5%BD%95)**
作者:石\_a41c
鏈接:https://www.jianshu.com/p/6439e94b28b9
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
## 表達式
### [使用恒等式](https://links.jianshu.com/go?to=http%3A%2F%2Fphp.net%2Fmanual%2Fen%2Flanguage.operators.comparison.php)
**不好:**
簡易對比會將字符串轉為整形
~~~php
$a = '42';
$b = 42;
if( $a != $b ) {
//這里始終執行不到
}
~~~
對比 b 返回了 `FALSE` 但應該返回 `TRUE` !
字符串 '42' 跟整數 42 不相等
**好:**
使用恒等判斷檢查類型和數據
~~~php
$a = '42';
$b = 42;
if ($a !== $b) {
// The expression is verified
}
~~~
The comparison `$a !== $b` returns `TRUE`.
**[? 返回頂部](#%E7%9B%AE%E5%BD%95)**
## 函數
### 函數參數(最好少于2個)
限制函數參數個數極其重要,這樣測試你的函數容易點。有超過3個可選參數參數導致一個爆炸式組合增長,你會有成噸獨立參數情形要測試。
無參數是理想情況。1個或2個都可以,最好避免3個。再多就需要加固了。通常如果你的函數有超過兩個參數,說明他要處理的事太多了。 如果必須要傳入很多數據,建議封裝一個高級別對象作為參數。
**壞:**
~~~php
function createMenu(string $title, string $body, string $buttonText, bool $cancellable): void
{
// ...
}
~~~
**好:**
~~~php
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
{
// ...
}
~~~
**[? 返回頂部](#%E7%9B%AE%E5%BD%95)**
### 函數應該只做一件事
這是迄今為止軟件工程里最重要的一個規則。當一個函數做超過一件事的時候,他們就難于實現、測試和理解。當你把一個函數拆分到只剩一個功能時,他們就容易被重構,然后你的代碼讀起來就更清晰。如果你光遵循這條規則,你就領先于大多數開發者了。
**壞:**
~~~php
function emailClients(array $clients): void
{
foreach ($clients as $client) {
$clientRecord = $db->find($client);
if ($clientRecord->isActive()) {
email($client);
}
}
}
~~~
**好:**
~~~php
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();
}
~~~
**[? 返回頂部](#%E7%9B%AE%E5%BD%95)**
### 函數名應體現他做了什么事
**壞:**
~~~php
class Email
{
//...
public function handle(): void
{
mail($this->to, $this->subject, $this->body);
}
}
$message = new Email(...);
// 啥?handle處理一個消息干嘛了?是往一個文件里寫嗎?
$message->handle();
~~~
**好:**
~~~php
class Email
{
//...
public function send(): void
{
mail($this->to, $this->subject, $this->body);
}
}
$message = new Email(...);
// 簡單明了
$message->send();
~~~
**[? 返回頂部](#%E7%9B%AE%E5%BD%95)**
### 函數里應當只有一層抽象abstraction
當你抽象層次過多時時,函數處理的事情太多了。需要拆分功能來提高可重用性和易用性,以便簡化測試。
(譯者注:這里從示例代碼看應該是指嵌套過多)
**壞:**
~~~php
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()`方法還是很復雜,而且不利于測試。
~~~php
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) {
// 解析邏輯...
}
}
~~~
**好:**
最好的解決方案是把 `parseBetterJSAlternative()`方法的依賴移除。
~~~php
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) {
// 解析邏輯...
}
}
}
~~~
這樣我們可以對依賴做mock,并測試`BetterJSAlternative::parse()`運行是否符合預期。
**[? 返回頂部](#%E7%9B%AE%E5%BD%95)**
### 不要用flag作為函數的參數
flag就是在告訴大家,這個方法里處理很多事。前面剛說過,一個函數應當只做一件事。 把不同flag的代碼拆分到多個函數里。
**壞:**
~~~php
function createFile(string $name, bool $temp = false): void
{
if ($temp) {
touch('./temp/'.$name);
} else {
touch($name);
}
}
~~~
**好:**
~~~php
function createFile(string $name): void
{
touch($name);
}
function createTempFile(string $name): void
{
touch('./temp/'.$name);
}
~~~
**[? 返回頂部](#%E7%9B%AE%E5%BD%95)**
### 避免副作用
一個函數做了比獲取一個值然后返回另外一個值或值們會產生副作用如果。副作用可能是寫入一個文件,修改某些全局變量或者偶然的把你全部的錢給了陌生人。
現在,你的確需要在一個程序或者場合里要有副作用,像之前的例子,你也許需要寫一個文件。你想要做的是把你做這些的地方集中起來。不要用幾個函數和類來寫入一個特定的文件。用一個服務來做它,一個只有一個。
重點是避免常見陷阱比如對象間共享無結構的數據,使用可以寫入任何的可變數據類型,不集中處理副作用發生的地方。如果你做了這些你就會比大多數程序員快樂。
**壞:**
~~~php
// Global variable referenced by following function.
// If we had another function that used this name, now it'd be an array and it could break it.
$name = 'Ryan McDermott';
function splitIntoFirstAndLastName(): void
{
global $name;
$name = explode(' ', $name);
}
splitIntoFirstAndLastName();
var_dump($name); // ['Ryan', 'McDermott'];
~~~
**好:**
~~~php
function splitIntoFirstAndLastName(string $name): array
{
return explode(' ', $name);
}
$name = 'Ryan McDermott';
$newName = splitIntoFirstAndLastName($name);
var_dump($name); // 'Ryan McDermott';
var_dump($newName); // ['Ryan', 'McDermott'];
~~~
**[? 返回頂部](#%E7%9B%AE%E5%BD%95)**
### 不要寫全局函數
在大多數語言中污染全局變量是一個壞的實踐,因為你可能和其他類庫沖突
并且調用你api的人直到他們捕獲異常才知道踩坑了。讓我們思考一種場景:
如果你想配置一個數組,你可能會寫一個全局函數`config()`,但是他可能
和試著做同樣事的其他類庫沖突。
**壞:**
~~~php
function config(): array
{
return [
'foo' => 'bar',
]
}
~~~
**好:**
~~~php
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;
}
}
~~~
加載配置并創建 `Configuration` 類的實例
~~~php
$configuration = new Configuration([
'foo' => 'bar',
]);
~~~
現在你必須在程序中用 `Configuration` 的實例了
**[? 返回頂部](#%E7%9B%AE%E5%BD%95)**
### 不要使用單例模式
單例是一種 [反模式](https://links.jianshu.com/go?to=https%3A%2F%2Fen.wikipedia.org%2Fwiki%2FSingleton_pattern). 以下是解釋:Paraphrased from Brian Button:
1. 總是被用成全局實例。
3. 導致代碼強耦合
4. 在整個程序的生命周期中始終攜帶狀態。
這里有一篇非常好的討論單例模式的\[根本問題(([http://misko.hevery.com/2008/08/25/root-cause-of-singletons/](https://links.jianshu.com/go?to=http%3A%2F%2Fmisko.hevery.com%2F2008%2F08%2F25%2Froot-cause-of-singletons%2F))的文章,是[Misko Hevery](https://links.jianshu.com/go?to=http%3A%2F%2Fmisko.hevery.com%2Fabout%2F) 寫的。
**壞:**
~~~php
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();
~~~
**好:**
~~~php
class DBConnection
{
public function __construct(string $dsn)
{
// ...
}
// ...
}
~~~
創建 `DBConnection` 類的實例并通過 [DSN](https://links.jianshu.com/go?to=http%3A%2F%2Fphp.net%2Fmanual%2Fen%2Fpdo.construct.php%23refsect1-pdo.construct-parameters) 配置.
~~~php
$connection = new DBConnection($dsn);
~~~
現在你必須在程序中 使用 `DBConnection` 的實例了
**[? 返回頂部](#%E7%9B%AE%E5%BD%95)**
### 封裝條件語句
**壞:**
~~~php
if ($article->state === 'published') {
// ...
}
~~~
**好:**
~~~php
if ($article->isPublished()) {
// ...
}
~~~
**[? 返回頂部](#%E7%9B%AE%E5%BD%95)**
### 避免用反義條件判斷
**壞:**
~~~php
function isDOMNodeNotPresent(\DOMNode $node): bool
{
// ...
}
if (!isDOMNodeNotPresent($node))
{
// ...
}
~~~
**好:**
~~~php
function isDOMNodePresent(\DOMNode $node): bool
{
// ...
}
if (isDOMNodePresent($node)) {
// ...
}
~~~
**[? 返回頂部](#%E7%9B%AE%E5%BD%95)**
### 避免條件判斷
這看起來像一個不可能任務。當人們第一次聽到這句話是都會這么說。
"沒有`if語句`我還能做啥?" 答案是你可以使用多態來實現多種場景
的相同任務。第二個問題很常見, “這么做可以,但為什么我要這么做?”
答案是前面我們學過的一個Clean Code原則:一個函數應當只做一件事。
當你有很多含有`if`語句的類和函數時,你的函數做了不止一件事。
記住,只做一件事。
**壞:**
~~~php
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();
}
}
}
~~~
**好:**
~~~php
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();
}
}
~~~
**[? 返回頂部](#%E7%9B%AE%E5%BD%95)**
### 避免類型檢查 (part 1)
PHP是弱類型的,這意味著你的函數可以接收任何類型的參數。
有時候你為這自由所痛苦并且在你的函數漸漸嘗試類型檢查。
有很多方法去避免這么做。第一種是統一API。
**壞:**
~~~php
function travelToTexas($vehicle): void
{
if ($vehicle instanceof Bicycle) {
$vehicle->pedalTo(new Location('texas'));
} elseif ($vehicle instanceof Car) {
$vehicle->driveTo(new Location('texas'));
}
}
~~~
**好:**
~~~php
function travelToTexas(Traveler $vehicle): void
{
$vehicle->travelTo(new Location('texas'));
}
~~~
**[? 返回頂部](#%E7%9B%AE%E5%BD%95)**
作者:石\_a41c
鏈接:https://www.jianshu.com/p/6439e94b28b9
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
### 避免類型檢查 (part 1)
PHP是弱類型的,這意味著你的函數可以接收任何類型的參數。
有時候你為這自由所痛苦并且在你的函數漸漸嘗試類型檢查。
有很多方法去避免這么做。第一種是統一API。
**壞:**
~~~php
function travelToTexas($vehicle): void
{
if ($vehicle instanceof Bicycle) {
$vehicle->pedalTo(new Location('texas'));
} elseif ($vehicle instanceof Car) {
$vehicle->driveTo(new Location('texas'));
}
}
~~~
**好:**
~~~php
function travelToTexas(Traveler $vehicle): void
{
$vehicle->travelTo(new Location('texas'));
}
~~~
**[? 返回頂部](#%E7%9B%AE%E5%BD%95)**
### 避免類型檢查 (part 2)
如果你正使用基本原始值比如字符串、整形和數組,要求版本是PHP 7+,不用多態,需要類型檢測,
那你應當考慮[類型聲明](https://links.jianshu.com/go?to=http%3A%2F%2Fphp.net%2Fmanual%2Fen%2Ffunctions.arguments.php%23functions.arguments.type-declaration)或者嚴格模式。
提供了基于標準PHP語法的靜態類型。 手動檢查類型的問題是做好了需要好多的廢話,好像為了安全就可以不顧損失可讀性。
保持你的PHP 整潔,寫好測試,做好代碼回顧。做不到就用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;
}
~~~
**好:**
~~~php
function combine(int $val1, int $val2): int
{
return $val1 + $val2;
}
~~~
**[? 返回頂部](#%E7%9B%AE%E5%BD%95)**
### 移除僵尸代碼
僵尸代碼和重復代碼一樣壞。沒有理由保留在你的代碼庫中。如果從來沒被調用過,就刪掉!
因為還在代碼版本庫里,因此很安全。
**壞:**
~~~php
function oldRequestModule(string $url): void
{
// ...
}
function newRequestModule(string $url): void
{
// ...
}
$request = newRequestModule($requestUrl);
inventoryTracker('apples', $request, 'www.inventory-awesome.io');
~~~
**好:**
~~~php
function requestModule(string $url): void
{
// ...
}
$request = requestModule($requestUrl);
inventoryTracker('apples', $request, 'www.inventory-awesome.io');
~~~
**[? 返回頂部](#%E7%9B%AE%E5%BD%95)**
## 對象和數據結構
### 使用 getters 和 setters
在PHP中你可以對方法使用`public`, `protected`, `private` 來控制對象屬性的變更。
* 當你想對對象屬性做獲取之外的操作時,你不需要在代碼中去尋找并修改每一個該屬性訪問方法
* 當有`set`對應的屬性方法時,易于增加參數的驗證
* 封裝內部的表示
* 使用set*和get*時,易于增加日志和錯誤控制
* 繼承當前類時,可以復寫默認的方法功能
* 當對象屬性是從遠端服務器獲取時,get*,set*易于使用延遲加載
此外,這樣的方式也符合OOP開發中的[開閉原則](#%E5%BC%80%E9%97%AD%E5%8E%9F%E5%88%99)
**壞:**
~~~php
class BankAccount
{
public $balance = 1000;
}
$bankAccount = new BankAccount();
// Buy shoes...
$bankAccount->balance -= 100;
~~~
**好:**
~~~php
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();
~~~
**[? 返回頂部](#%E7%9B%AE%E5%BD%95)**
### 給對象使用私有或受保護的成員變量
* 對`public`方法和屬性進行修改非常危險,因為外部代碼容易依賴他,而你沒辦法控制。**對之修改影響所有這個類的使用者。** `public`
* 對`protected`的修改跟對`public`修改差不多危險,因為他們對子類可用,他倆的唯一區別就是可調用的位置不一樣,**對之修改影響所有集成這個類的地方。** `protected`
* 對`private`的修改保證了這部分代碼**只會影響當前類**`private`
所以,當你需要控制類里的代碼可以被訪問時才用`public/protected`,其他時候都用`private`。
可以讀一讀這篇 [博客文章](https://links.jianshu.com/go?to=http%3A%2F%2Ffabien.potencier.org%2Fpragmatism-over-theory-protected-vs-private.html) ,[Fabien Potencier](https://links.jianshu.com/go?to=https%3A%2F%2Fgithub.com%2Ffabpot)寫的.
**壞:**
~~~php
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
~~~
**好:**
~~~php
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
~~~
**[? 返回頂部](#%E7%9B%AE%E5%BD%95)**
## 類
### 少用繼承多用組合
正如 the Gang of Four 所著的[*設計模式*](https://links.jianshu.com/go?to=https%3A%2F%2Fen.wikipedia.org%2Fwiki%2FDesign_Patterns)之前所說,
我們應該盡量優先選擇組合而不是繼承的方式。使用繼承和組合都有很多好處。
這個準則的主要意義在于當你本能的使用繼承時,試著思考一下`組合`是否能更好對你的需求建模。
在一些情況下,是這樣的。
接下來你或許會想,“那我應該在什么時候使用繼承?”
答案依賴于你的問題,當然下面有一些何時繼承比組合更好的說明:
1. 你的繼承表達了“是一個”而不是“有一個”的關系(人類-》動物,用戶-》用戶詳情)
2. 你可以復用基類的代碼(人類可以像動物一樣移動)
3. 你想通過修改基類對所有派生類做全局的修改(當動物移動時,修改她們的能量消耗)
**糟糕的:**
~~~php
class Employee
{
private $name;
private $email;
public function __construct(string $name, string $email)
{
$this->name = $name;
$this->email = $email;
}
// ...
}
// 不好,因為 Employees "有" taxdata
// 而 EmployeeTaxData 不是 Employee 類型的
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;
}
// ...
}
~~~
**好:**
~~~php
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);
}
// ...
}
~~~
**[? 返回頂部](#%E7%9B%AE%E5%BD%95)**
### 避免連貫接口
[連貫接口Fluent interface](https://links.jianshu.com/go?to=https%3A%2F%2Fen.wikipedia.org%2Fwiki%2FFluent_interface)是一種
旨在提高面向對象編程時代碼可讀性的API設計模式,他基于[方法鏈Method chaining](https://links.jianshu.com/go?to=https%3A%2F%2Fen.wikipedia.org%2Fwiki%2FMethod_chaining)
有上下文的地方可以降低代碼復雜度,例如[PHPUnit Mock Builder](https://links.jianshu.com/go?to=https%3A%2F%2Fphpunit.de%2Fmanual%2Fcurrent%2Fen%2Ftest-doubles.html)
和[Doctrine Query Builder](https://links.jianshu.com/go?to=http%3A%2F%2Fdocs.doctrine-project.org%2Fprojects%2Fdoctrine-dbal%2Fen%2Flatest%2Freference%2Fquery-builder.html)
,更多的情況會帶來較大代價:
While there can be some contexts, frequently builder objects, where this
pattern reduces the verbosity of the code (for example the [PHPUnit Mock Builder](https://links.jianshu.com/go?to=https%3A%2F%2Fphpunit.de%2Fmanual%2Fcurrent%2Fen%2Ftest-doubles.html)
or the [Doctrine Query Builder](https://links.jianshu.com/go?to=http%3A%2F%2Fdocs.doctrine-project.org%2Fprojects%2Fdoctrine-dbal%2Fen%2Flatest%2Freference%2Fquery-builder.html)),
more often it comes at some costs:
1. 破壞了 [對象封裝](https://links.jianshu.com/go?to=https%3A%2F%2Fen.wikipedia.org%2Fwiki%2FEncapsulation_%2528object-oriented_programming%2529)
2. 破壞了 [裝飾器模式](https://links.jianshu.com/go?to=https%3A%2F%2Fen.wikipedia.org%2Fwiki%2FDecorator_pattern)
3. 在測試組件中不好做[mock](https://links.jianshu.com/go?to=https%3A%2F%2Fen.wikipedia.org%2Fwiki%2FMock_object)
4. 導致提交的diff不好閱讀
了解更多請閱讀 [連貫接口為什么不好](https://links.jianshu.com/go?to=https%3A%2F%2Focramius.github.io%2Fblog%2Ffluent-interfaces-are-evil%2F)
,作者 [Marco Pivetta](https://links.jianshu.com/go?to=https%3A%2F%2Fgithub.com%2FOcramius).
**壞:**
~~~php
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();
~~~
**好:**
~~~php
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();
~~~
**[? 返回頂部](#%E7%9B%AE%E5%BD%95)**
### 推薦使用 final 類
能用時盡量使用 `final` 關鍵字:
1. 阻止不受控的繼承鏈
2. 鼓勵 [組合](#%E5%B0%91%E7%94%A8%E7%BB%A7%E6%89%BF%E5%A4%9A%E7%94%A8%E7%BB%84%E5%90%88).
3. 鼓勵 [單一職責模式](#%E5%8D%95%E4%B8%80%E8%81%8C%E8%B4%A3%E6%A8%A1%E5%BC%8F).
4. 鼓勵開發者用你的公開方法而非通過繼承類獲取受保護方法的訪問權限.
5. 使得在不破壞使用你的類的應用的情況下修改代碼成為可能.
**Bad:**
~~~php
final class Car
{
private $color;
public function __construct($color)
{
$this->color = $color;
}
/**
* @return string The color of the vehicle
*/
public function getColor()
{
return $this->color;
}
}
~~~
**Good:**
~~~php
interface Vehicle
{
/**
* @return string The color of the vehicle
*/
public function getColor();
}
final class Car implements Vehicle
{
private $color;
public function __construct($color)
{
$this->color = $color;
}
/**
* {@inheritdoc}
*/
public function getColor()
{
return $this->color;
}
}
~~~
## SOLID
**SOLID** 是Michael Feathers推薦的便于記憶的首字母簡寫,它代表了Robert Martin命名的最重要的五個面對對象編碼設計原則
* [S: 單一職責原則 (SRP)](#%E8%81%8C%E8%B4%A3%E5%8E%9F%E5%88%99)
* [O: 開閉原則 (OCP)](#%E5%BC%80%E9%97%AD%E5%8E%9F%E5%88%99)
* [L: 里氏替換原則 (LSP)](#%E9%87%8C%E6%B0%8F%E6%9B%BF%E6%8D%A2%E5%8E%9F%E5%88%99)
* [I: 接口隔離原則 (ISP)](#%E6%8E%A5%E5%8F%A3%E9%9A%94%E7%A6%BB%E5%8E%9F%E5%88%99)
* [D: 依賴倒置原則 (DIP)](#%E4%BE%9D%E8%B5%96%E5%80%92%E7%BD%AE%E5%8E%9F%E5%88%99)
### 單一職責原則
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 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
}
}
~~~
**好:**
不是每一個工人都是雇員,但是每一個雇員都是一個工人
~~~php
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
}
}
~~~
### 依賴倒置原則
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();
}
}
~~~
**[? 返回頂部](#%E7%9B%AE%E5%BD%95)**
## 別寫重復代碼 (DRY)
盡你最大的努力去避免復制代碼,它是一種非常糟糕的行為,復制代碼
通常意味著當你需要變更一些邏輯時,你需要修改不止一處。
**壞:**
~~~php
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);
}
}
~~~
**好:**
~~~php
function showList(array $employees): void
{
foreach ($employees as $employee) {
$expectedSalary = $employee->calculateExpectedSalary();
$experience = $employee->getExperience();
$githubLink = $employee->getGithubLink();
$data = [
$expectedSalary,
$experience,
$githubLink
];
render($data);
}
}
~~~
**極好:**
最好讓代碼緊湊一點
~~~php
function showList(array $employees): void
{
foreach ($employees as $employee) {
render([
$employee->calculateExpectedSalary(),
$employee->getExperience(),
$employee->getGithubLink()
]);
}
}
~~~