## 類
### 組合優于繼承
正如 the Gang of Four 所著的[*設計模式*](https://en.wikipedia.org/wiki/Design_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);
}
// ...
}
```
### 避免連貫接口
連貫接口在某些地方可能稱之為鏈式方法,多數人可能覺得比較好,但是好與壞是需要實踐得出結論。
```php
//連貫接口
$queryBuilder
->select('id', 'name')
->from('users')
->where('email = ?')
->setParameter(0, $userInputEmail);
```
[連貫接口Fluent interface](https://en.wikipedia.org/wiki/Fluent_interface)是一種旨在提高面向對象編程時代碼可讀性的API設計模式,基于[方法鏈Method chaining](https://en.wikipedia.org/wiki/Method_chaining)
在有上下文的地方可以降低代碼復雜度,例如[PHPUnit Mock Builder](https://phpunit.de/manual/current/en/test-doubles.html)
和[Doctrine Query Builder](http://docs.doctrine-project.org/projects/doctrine-dbal/en/latest/reference/query-builder.html),但是更多的情況會帶來較大代價:
1. 破壞了 [對象封裝](https://en.wikipedia.org/wiki/Encapsulation_%28object-oriented_programming%29)
2. 破壞了 [裝飾器模式](https://en.wikipedia.org/wiki/Decorator_pattern)
3. 在測試組件中不好做[mock](https://en.wikipedia.org/wiki/Mock_object)
4. 導致提交的diff不好閱讀
了解更多請閱讀 [Marco Pivetta](https://github.com/Ocramius)寫的[連貫接口為什么不好](https://ocramius.github.io/blog/fluent-interfaces-are-evil/)。
**差:**
```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();
```
### 推薦使用 final 類
能用時盡量使用 [final](http://php.net/manual/zh/language.oop5.final.php) 關鍵字,多數人可:
1. 阻止不受控的繼承鏈
2. 鼓勵 [組合](#prefer-composition-over-inheritance).
3. 鼓勵 [單一職責模式](#single-responsibility-principle-srp).
4. 鼓勵開發者用你的公開方法而非通過繼承類獲取受保護方法的訪問權限.
5. 使得在不破壞使用你的類的應用的情況下修改代碼成為可能.
**差:**
```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;
}
}
```
**優:**
```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;
}
}
```