## 类
### 组合优于继承
正如 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;
}
}
```