PHP整洁之道
翻译github项目 Clean Code PHP,能力有限,大家见笑。
目录
- 介绍
- 变量
- 函数
- 对象和数据结构
- 类
a. S: 单一功能 Single Responsibility Principle (SRP)
b. O: 开闭原则 Open/Closed Principle (OCP)
c. L: 里氏替换 Liskov Substitution Principle (LSP)
d. I: 接口隔离 Interface Segregation Principle (ISP)
e. D: 依赖反转 Dependency Inversion Principle (DIP) - 不要重复工作
介绍
Robert C. Martin的书《Clean Code》讲述了软件工程的原则,同样也适用于PHP。它不仅仅是代码风格上的指导,同样也是告诉我们如何用PHP写出可读、可重用、可重构的软件的指导。
也不是所有的原则都必须严格遵守,还有一些是普遍约定。这些都是指导方针,仅此而已。但他们也同样是《Clean Code》的作者多年收集整理的心得。
变量
使用有意义和易拼写的变量名
不好的例子
$ymdstr = $moment->format('y-m-d');
好的例子
$currentDate = $moment->format('y-m-d');
对于相同类型的变量使用相同词汇
不好的例子
getUserInfo();
getClientData();
getCustomerRecord();
好的例子
getUser();
使用易理解的变量名
读代码总是比写代码要多。所以我们写的代码要具备很高的可读性和容易理解。不对变量做有助于理解我们项目的命名,最终会伤害我们的读者。
不好的例子
//86400 是什么意思呢?
addExpireAt(86400);
好的例子
// 将他们声明为大写的`const`全局变量
interface DateGlobal{
const SECONDS_IN_A_DAY = 86400;
}
addExpireAt(DateGlobal::SECONDS_IN_A_DAY);
使用解释型的变量
不好的例子
$address = 'One Infinite Loop, Gupertino 95014';
$cityZipCodeRegex = '/^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/';
preg_match($cityZipCodeRegex, $address, $matches);
saveCityZipCode($matches[1], $matches[2]);
一般的例子
它更好一点,但我们仍然严重依赖regex
$address = 'One Infinite Loop, Cupertino 95014';
$cityZipCodeRegex = '/^[^,\\]+[,\\\s]+(.+?)\s*(\d{5})?$/';
preg_match($cityZipCodeRegex, $address, $matches);
list(, $city, $zipCode) = $matches;
saveCodeZip($city, $zipCode);
好的例子
通过命名子模式减少对regex的依赖
$address = 'One Infinite Loop, Cupertino 95014';
$cityZipCodeRegex = '/^[^,\\]+[,\\\s]+(?<city>.+?)\s*(?<zipCode>\d{5})?$/';
preg_match($cityZipCodeRegex, $address, $matches);
saveCityZip($matches['city'], $matches['zipCode']);
避免无意义的变量名
别让别人猜你的代码是什么意思。
显式比隐式更好。
不好的例子
$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 Fransisco'];
foreach ($locations as $location) {
doStuff();
doSomeOtherStuff();
//...
//...
//...
dispatch($location);
}
不添加不必要的信息
如果你的类/对象名表示了某种含义,不需要在你的变量名中重复。
不好的例子
class Car {
public $carMake;
public $carModel;
public $carColor;
//...
}
好的例子
class Car {
public $make;
public $model;
public $color;
//...
}
使用默认参数代替条件
不好的例子
function createMicrobrewery($name = null) {
$breweryName = $name ?: 'Hipster Brew Co.';
//...
}
好的例子
function createMicrobrewery($breweryName = 'Hipster Brew Co.') {
//...
}
函数
函数的参数(最好少于2个)
限制函数参数的数量是非常重要的,因为这会使得测试函数更容易。3个以上的参数会导致你需要测试大量不同的情况。
无参数是最理想的。一个或者两个参数也是很好的,三个就应该避免了。任何超过这一点的都需要重写。通常,如果你需要两个以上参数时你的函数需要做更多处理。这种时候,更高级的对象就可以作为一个参数了。
不好的例子
function createMenu($title, $body, $buttonText, $cancellable) {
//...
}
好的例子
class MenuConfig {
public $title;
public $body;
public $buttonText;
public $cacellable = false;
}
$config = new MenuConfig();
$config->title = 'Foo';
$config->body = 'Bar';
$config->buttonText = 'Baz';
$config->cancellable = true;
function creataMenu(MenuConfig $config) {
//...
}
一个函数应该只做一件事情
这是目前软件工程中最重要的规则。当一个函数做不止一件事情的之后,他们会更难去构成、测试和理解。你的每个函数只做一件事情的时候,你会更容易地去重构,并且你的代码读起来会干净很多。如果你把这个规则看得比其他规则重要很多,你就会领先其他很多开发人员了。
不好的例子
function emailClients($clients) {
foreach ($clients as $client) {
$clientRecord = $db->find($client);
if ($clientRecord->isActive()) {
email($client);
}
}
}
好的例子
function emailClients($clients) {
$activeClients = activeClients($clients);
array_walk($activeClients, 'email');
}
function activeClients($clients) {
return array_walk($clients, 'isClientActive');
}
function isClientActive($clients) {
$clientRecord = $db->find($client);
return $clientRecord->isActive();
}
函数名应该告诉我们它做的是什么
不好的例子
class Email {
//...
public function handle() {
mail($this->to, $this->subject, $this->body);
}
}
$message = new Email(...);
// 这是什么?信息`message`的处理`handle`?我们正在写文件嘛?
$message->handle();
好的例子
class Email {
//...
public function send() {
mail($this->to, $this->subject, $this-<body);
}
}
$message = new Email(...);
// 简洁清楚
$message->send();
函数只能是一个抽象层次
当你的函数具有多个抽象层次时,你需要做的事会更多。拆分你的函数,会使得它更容易被重用且更容易测试。
不好的例子
function parseBetterJSAlternative($code) {
$regexes = [
// ...
];
$statements = split(' ', $code);
$tokens = [];
foreach ($regeses as $regex) {
foreach ($statements as $statement) {
// ...
}
}
$ast = [];
foreach ($tokens as $token) {
// lex...
}
foreach ($ast as node) {
// parse...
}
}
还是不好的例子
我们已经拆分出了一些函数,但是parseBetterJSAlternative()
函数还是非常负责和难以测试。
function tokenize($code) {
$regexes = [
// ...
];
$statements = split(' ', $code);
$tokens = [];
foreach ($regexes as $regex) {
foreach ($statements as $statement) {
$token[] = /* ... */;
}
}
return $tokens;
}
function leser($tokens) {
$ast = [];
foreach ($tokens as $token) {
$ast[] = /* ...*/;
}
return $ast;
}
function parseBetterJSAlternative($code) {
$tokens = tokenize($code);
$ast = lexer($tokens);
foreach ($ast as $node) {
// parse...
}
}
好的例子
最好的方法就是把parseBetterJSAlternative()
方法的依赖关系移除出去。
class Tokenizer {
public function tokenize($code) {
$regexes = [
// ...
];
$statements = split(' ', $code);
$tokens = [];
foreach ($regexes as $regex) {
foreach ($statements as $statement) {
$tokens[] = /* ... */;
}
}
return $tokens;
}
}
class Lexer {
public function lexify($tokens) {
$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 ($code) {
$token = $this->tokenizer->tokenize($code);
$ast = $this->lexer->lexify($tokens);
foreach ($ast as $node) {
// parse...
}
}
}
现在我们可以模拟依赖关系并只需要测试BetterJSAlternative::parse()
方法的工作了。
不使用标志作为函数的参数
标志告诉用户函数做了不止一件事。函数应该只做一件事。如果它们遵循基于布尔值boolean
的代码路径,那就拆分它们。
不好的例子
function createFile($name, $temp = false) {
if ($temp) {
touch('./temp/' . $name);
} else {
touch($name);
}
}
好的例子
function createFile($name) {
touch($name);
}
function creataTempFile($name) {
touch('./temp/' . $name);
}
避免副作用
一个函数如果再做其他事情而不是取一个值然后返回一个或这多个值时产生了副作用。副作用可能是写入文件,修改一些全局变量,或者把你得钱转给了陌生人。
现在,你的项目中有时确实需要一些副作用。就像上一个例子,你可能需要去写文件。你需要做的就是去集中你在哪做这件事。不需要有多个函数和类来写入特定的文件。有一个服务来做这件事就可以了,只需要一个。
重点是避免常见的陷阱,比如在没有任何结构的对象之间共享状态,使用可以被任何东西写入的可变数据类型,而不是集中在你副作用发生的地方。如果你能做到这一点,你会比绝大多数程序员快乐。
不好的例子
// 由一下函数引用的全局变量
// 如果我们有另一个函数使用了这个名字,它会成为一个数组,并且会产生溢出。
$name = 'Ryan McDermott';
function splitIntoFirstAndLastName() {
global $name;
$name = preg_split('/ /', $name);
}
splitIntoFirstAndLastName();
var_dump($name); // ['Ryan', 'McDermott'];
好的例子
function splitIntoFirstAndLastName($name) {
return preg_split('/ /', $name);
}
$name = 'Ryan McDermott';
$newName = splitIntoFirstAndLastName($name);
var_dump($name); // 'Ryan McDermott';
var_dump($newName) // ['Ryan', 'McDermott'];
不要写入全局函数
在很多语言中,泛滥的全局资源是一种很糟糕的体验,因为你可能和另一个库发生冲突,如果你的API用户在生产中得到了一个异常,那就很不明智了。让我们试想一个场景:如果你想要配置数组你会做什么。你可能会写一个全局函数像config()
,但是它可能会与其他试图做同一件事的库冲突。
不好的例子
function config() {
return [
'foo' => 'bar',
];
}
好的例子
创建PHP配置文件或者其他的
// config.php
return [
'foo' => 'bar',
];
class Configuration {
private $configuration = [];
public function __construct(array $configuration) {
$this->configuration = $configuration;
}
public function get($key) {
return isset($this->configuration[$key]) ? $this->configuration[$key] : null;
}
}
从文件中加载配置并创建Configuration
实例
$configuration = new Configuration($configuration);
现在你必须在你的应用中使用Configuration
实例。
不适用单例模式
单例是一种反面模式 anti-pattern。
不好的例子
class DBConnection {
private static $instance;
private function __construct($dsn) {
// ...
}
public static function getInstance() {
if (self::$instance === null) {
self:$instance = new self();
}
return self:$instance;
}
// ...
}
好的例子
class DBConnection {
public function __construct(array $dsn) {
// ...
}
// ...
}
创建DBConnection
实例并且使用DSN来配置。
$connect = new DBConnection($dsn);
现在你必须在你的应用中使用Configuration
实例。
封装条件
不好的例子
if ($article->state === 'published') {
// ...
}
好的例子
if ($article->isPublished()) {
// ...
}
避免反条件
不好的例子
function isDOMNodePresent($node) {
// ...
}
if (!isDOMNodePresent($node)) {
// ...
}
好的例子
function isDOMNodePresent($node) {
// ...
}
if (isDOMNodePresent($node)) {
// ...
}
避免条件
看起来像个不可能完成的任务。听到这个,大多数人会说:“如果没有if
声明,我该怎么做呢?”答案是,在许多情况下,可以使用多态来完成相同的工作。第二个问题通常是“这真是太棒了,但是我为什么要这么做呢?”答案是我们之前学过的一个代码整洁之道的概念:一个函数只能做一件事。当你有包含if
的类和函数时,你在告诉用户你的函数不止在做一件事。记住,只做一件事。
不好的例子
class Airplane {
// ...
public function getCruisingAltitude() {
switch ($this->type) {
case "777":
return $this->getMaxAltitude() - $this->getPassengerCount();
case "Air Force One":
return $this->getMaxAltitude();
case "Cessna":
return $this->getMaxAltitude() - $this->getFuelExpenditure();
}
}
}
好的例子
class Airplane {
// ...
}
class Boeing777 extends Airplane {
// ...
public function getCruisingAltitude() {
return $this->getMaxAltitude() - $this->getPassengerCount();
}
}
class AirForceOne extends Airplane {
// ...
public function getCruisingAltitude() {
return $this->getMaxAltitude();
}
}
class Cessna extends Airplane {
// ...
public function getCruisingAltitude() {
return $this->getMaxAltitude() - $this->getFuelExpenditure();
}
}
避免类型检查type-checking
(第一部分)
PHP是弱类型语言,意思是你的函数可以接受任何类型的参数。有时你会被这种自由所干扰,你不得不在你的函数中进行类型检查。有很多种方法可以避免你去做这些。首先要考虑的事一致的API。
不好的例子
function travelToTexas($vehicle) {
if ($vehicle instanceof Bicycle) {
$vehicle->peddleTo(new Location('texas'));
} else {
$vehicle->driveTo(new Location('texas'));
}
}
好的例子
function travelToTexas(Traveler $vehicle) {
$vehicle->travelTo(new Location('texas'));
}
避免类型检查 (第二部分)
如果只使用像字符、整数、数组这样的基本的原始值,使用PHP 7+,不使用多态,依旧觉得需要类型检查,那么你该考虑看看这本书《type declaration》或者使用严格模式。它提供了标准PHP语法之上的静态类型。手工类型检查的问题在于,要做得很好,需要大量额外的verbiage,而您所得到的“类型安全”并不能弥补失去的可读性。保持你的PHP整洁,编写好的测试样例和良好的代码审查。否则,您可以使用PHP严格的类型声明或严格模式来完成这些工作。
不好的例子
function combine($val, $val) {
if (!is_numeric($val1) || !is_numeric($val)) {
throw new \Exception('Must be of type Number');
}
}
好的例子
function combine(int $val1, int $val2) {
return $val1 + $val2;
}
删除无效代码
无效代码和重复代码一样糟糕。没有理由把他们留在代码当中。如果不被调用,那就删除它们。如果你需要的话你可以在你的历史版本中找到他们。
不好的例子
function oldRequestModule($url) {
// ...
}
function newRequestModule($url) {
// ...
}
$request = newRequestModuel($requestUrl);
inventoryTracker('apples', $request, 'www.inventory-awesome.io');
好的例子
function requestModule($url) {
// ...
}
$request = newRequestModuel($requestUrl);
inventoryTracker('apples', $request, 'www.inventory-awesome.io');
对象和数据结构
使用getter
和setter
方法
在PHP语言中,你可以使用public
、protected
、private
关键字来修饰方法。使用这些,你可以控制对象的属性修改权限。
- 当您想要做更多的事情而不仅仅是获得一个对象属性时,您就不必在代码库中查找并更改每个访问器。
- 使用set
使得添加验证更加方便
- 内部封装
- 在输入set
和输出get
是更容易添加日志记录和错误处理。
- 继承这个类,你可以重写默认函数。
- 您可以延迟加载对象的属性,比方说从服务器获取它。
此外,这是面向对象设计原则中开放/封闭原则的一部分。
不好的例子
class BankAccount {
public $balance = 1000;
}
$bankAccount = new BankAccount();
// 买鞋...
$bankAccount->balance -= 100;
好的例子
class BankAccount {
private $balance;
public function __construct($balance = 1000) {
$this->balance = $balance;
}
public function withdrawBalance($amount) {
if ($amount > $this->balance) {
throw new \Exception('Amount greater than available balance');
}
$this->balance -= $amount;
}
public function depositBalance($amount) {
$this->baance += $amount;
}
public function getBalance() {
return $this->balance;
}
$bankAccount = new BankAccount();
// 买鞋...
$bankAccount->withdrawBalance($shoesPrice);
// 获取余额
$balance = $bankAccount->getBalance();
}
对象要有private
/protected
成员
不好的例子
class Employee {
public $name;
public function __construct($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($name) {
$this->name = $name;
}
public function getName() {
return $this->name;
}
}
$employee = new Employee('John Doe');
echo "Employee name: " . $employee->getName(); // Employee name: John Doe
类
单一职责原则(SRP)
正如《代码整洁之道》中所述,“一个类的变化不应该有多个原因”。用很多功能来包装一个类是很有诱惑力的,就像你在航班上智能携带一个手提箱。这样做的问题是,这个类就不再具有内聚性,有很多理由会使它发生改变。减少修改类的次数是很重要的。因为一个类有太多功能的话,你在修改了其中一部分时,你会很难理解它会如何影响到你代码中的其它依赖部分。
不好的例子
class UserSettings {
private $user;
public function __construct($user) {
$this->user = $user;
}
public function changeSettings($settings) {
if ($this->verifyCredentials()) {
// ...
}
}
private function verifyCredentials() {
// ...
}
}
好的例子
class UserAuth {
private $user;
public function __construct($user) {
$this->user = $user;
}
public function verifyCredentials() {
// ...
}
}
class UserSettings {
private $user;
private $auth;
public function __construct($user) {
$this->user = $user;
$this->auth = new UserAuth($user);
}
public function changeSettings($settings) {
if ($this->auth->verifyCredentials()) {
// ...
}
}
}
开放/关闭原则 (OCP)
正如Bertrand Meyer所说,“软件实体(类、模块、函数等等)应该对扩展开放,而对修改关闭。”这是什么意思呢?这个原则告诉我们应该允许用户在不修改原有代码的情况下添加新函数。
不好的例子
abstract class Adapter {
protected $name;
public function getName() {
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) {
$this->adapter = $adapter;
}
public function fetch($url) {
$adapterName = $this->adapter->getName();
if ($adapterName === 'ajaxAdapter') {
return $this->makeAjaxCall($url);
} elseif ($adapterName === 'nodeAdapter') {
return $this->makeHttpCall($url);
}
}
protected function makeAjaxCall($url) {
// request and return promise
}
protected function makeHttpCall($url) {
// request and return promise
}
}
好的例子
interface Adapter {
public function request($url);
}
class AjaxAdapter implements Adapter {
public function request($url) {
// request and return promise
}
}
class NodeAdapter implements Adapter {
public funciton request($url) {
// request and return promise
}
}
class HttpRequester {
private $adapter;
public function __construct($Adapter $adapter) {
$this->adapter = $adapter;
}
public function fetch($url) {
return $this->adapter->request($url);
}
}
里氏替换原则Liskov Substitution Principle
(LSP)
这是一个难以理解的术语。它被正式定义为“如果S是T的子类型,那么T类型的对象可以被S(即,S类型的对象可以替代T类型的对象)所替代而不会改变程序的任何可取属性(正确性,任务执行等等)”。这是一个更可怕的定义。
对于这个最好的解释就是:如果你有一个父类和一个子类,然后基类和子类可以互换使用而不会得到不正确的结果。这可能仍然令人疑惑,那么让我们来经典的矩形矩阵Square-Rectangle
的例子。
不好的例子
class Rectangle {
protected $width = 0;
protected $height = 0;
public function render($area) {
// ...
}
public function setWidth($width) {
$this->width = $width;
}
public function getHeight($height) {
$this->height = $height;
}
public function getArea() {
return $this->width * $this->height;
}
}
class Square extends Rectangle {
public function setWidth($width) {
$this->width = $this->height = $width;
}
public function setHeight($height) {
$this->width = $this->height = $height;
}
}
function renderLargeRectangles($rectangles) {
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);
}
}
$rectangle = [new Rectangle(), new Rectangle(), new Square()];
renderLargeRectangle($rectangles);
好的例子
abstract class Shape {
protected $width = 0;
protected $height =0;
abstract public function getArea();
public function render($area) {
// ...
}
}
class Rectangle extends Shape {
public function setWidth($width) {
$this->width = $width;
}
public function setHeight($height) {
$this->height = $height;
}
public function getArea() {
return $this->width * $this->height;
}
}
class Square extends Shape {
protected $length = 0;
public function setLength($length) {
$this->length = $length;
}
public function getArea() {
return pow($this->length, 2);
}
}
function renderLargeRectangle($rectangles) {
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()];
renderLargeRectangle($shapes);
接口隔离原则(ISP)
ISP指出“客户端不应该依赖未使用的接口”。
需要大型配置对象的类很好的说明了这个原则。不要求客户设置大量的选型是很好的,因为大多数时候我们并不需要所有的配置。让他们成为可选的可以有效防止出现接口臃肿。
不好的例子
interface Employee {
public function work();
public function eat();
}
class Human implements Employee {
public function work() {
// ... working
}
public function eat() {
// ... eating in lunch break
}
}
public Robot implements Employee {
public function work() {
// ... working much more
}
public function eat() {
// ... robot can't eating, but it must implement this method
}
}
好的例子
不是所有的worker
都是employee
, 但是所有的employee
都是worker
。
interface Workable {
public function work();
}
interface Feedable {
public function eat();
}
interface Employee extends Feedable, Workable { }
class Human implements Employee {
public function work() {
// ... working
}
public function eat() {
// ... eating in lunch break
}
}
// robot can only work
class Robot implements Workable {
public function work() {
// ...
}
}
依赖倒置原则(DIP)
有两点是很重要的:
1. 高级模块不应该依赖于底层模块,两者都应该依赖于抽象层。
2. 抽象不应该依赖于具象,具象应该依赖于抽象。
可能刚开始很难理解,但是如果你使用过PHP框架(例如Symfony),你应该在依赖注入(DI)中看到过这一原则的实现。虽然它并不是完全依赖原则实现的,但DIP使得高级模块并不需要了解低级模块也能够设置它们。这个可以通过DI来实现。很大一个好处是,它减少了模块之间的耦合。耦合是一种很糟糕的开发模式,因为它使得代码难以重构。
不好的例子
class Employee {
public function work() {
// ...
}
}
class Robot extends Employee {
public function work() {
// ... working much more
}
}
class Manager {
private $employee;
public function __construct(Employee $employee) {
$this->employee = $employee;
}
public function manage() {
$this->employee->work();
}
}
好的例子
interface Employee {
public function work();
}
class Human extends Employee {
public function work() {
// ... working
}
}
class Robot extends Employee {
public function work() {
// ... working nuch more
}
}
class Manager {
private $employee;
public function __construct(Employee $employee) {
$this->employee = $employee;
}
public function manage() {
$this->employee->work();
}
}
使用方法链
这个模式非常有用,通常会在许多库中使用,比如PHPUnit和Doctrine。它能让代码更具表达性,并且能减少冗余代码。由此,使用方法链你可以看到你的代码有多干净。在类函数中,只需要在每个set
函数末尾使用return $this
就可以把更多的类函数链接到它上。
不好的例子
class Car {
private $make = 'Honda';
private $model = 'Accord';
private $color = 'white';
public function setMake($make) {
$this->make = $make;
}
public function setModel($model) {
$this->model = $model;
}
public function setColor($color) {
$this->color = $color;
}
public function dump() {
var_dump($this->make, $this->model, $this->color);
}
}
$car = new Car();
$car->setColor('pink');
$car->setMake('Ford');
$car->setModel('F-150');
$car->cump();
好的例子
class Car {
private $make = 'Honda';
private $model = 'Accord';
private $color = 'white';
public function setMake($make) {
$this->make = $make;
//NOTE: Returning this for chaining
return $this;
}
public function setModel($model) {
$this->model = $model;
//NOTE: Returning this for chaining
return $this;
}
public function setColor($color) {
$this->color = $color;
//NOTE: Returning this for chaining
return $this;
}
public function dump() {
var_dump($this->make, $this->model, $this->color);
}
}
$car = (new Car())
->setColor('pink')
->setMake('Ford')
->setModel('F-150')
->dump();
组合比继承更好
正如Gang of Four在《设计模式》(Design Patterns)中所陈述的那样,应该尽可能地使用组合而不是继承,尽管组合和继承都有很多的优点。这句话告诉我们,当你想到要用继承的时候,你可以尝试想想组合是不是能够更好的解决你的问题。在某些情况下,是这样的。
你可能会疑惑,“那我什么时候使用继承呢?”这取决于你手头上的问题,下面有一个相当不错的列表告诉你什么时候继承比组合更有效。
1. 继承代表的是is-a
关系而不是has-a
关系(Human->Animal vs. User->UserDetails)。
2. 从基类中重用代码(人类可以像动物一样移动)。
3. 希望通过更改一个基类来对派生类进行全局更改(改变所有动物移动时的热量消耗)。
不好的例子
class Employee {
private $name;
private $emial;
public function __construct($name, $email) {
$this->name = $name;
$this->emial = $emial;
}
// ...
}
// Bad because Employees "have" tax data
// EmployeeTaxData is not a type of Employee
class EmployeeTaxData extends Employee {
private $ssn;
private $salary;
public function __construct($name, $email, $ssn, $salary) {
parent::__construct($name, $email);
$this->ssn = $ssnl
$this->salary = $salary;
}
// ...
}
好的例子
class EmployeeTaxData {
private $ssn;
private $salary;
public function __construct($ssn, $salary) {
$this->ssn = $ssn;
$this->salary = $salary;
}
// ...
}
class Employee {
private $name;
private $email;
private $taxData;
public function __construct($name, $email) {
$this->name = $name;
$this->email = $email;
}
public function setTaxData($ssn, $salary) {
$this->taxData = new EmployeeTaxData($ssn, $salary);
}
// ...
}
不要重复工作
请重视DRY
原则。
尽你最大努力避免重复代码。重复代码是很不好的,因为它意味着当你需要修改一些逻辑的时候你需要修改不止一处代码。
想象一下,你在经营一家餐馆,你会记录下所有的库存:西红柿、洋葱、大蒜、香料等等。如果你有很多这样的清单,当你端上一盘西红柿之后,你需要更新你所有的清单。如果你只有一张清单,你就只需要更新这一个地方。
重复代码一般是因为你有两个或者更多的很大部分一致但细微处有差别的工作,但是他们的不同之处迫使你使用了两个或者更多的功能差不多的函数来处理。删除重复代码意味着你需要创建一个抽象,使得你只需要一个类/模块/类就可以处理这类不同的事情。
提取抽象是至关重要的,这就是为什么你需要遵守在类 Classes
中所讲的SOLID原则。不好的抽象比重复代码更糟糕,所以一定要小心!说了这么多,如果能提取一个很好的抽象,那么就去做吧!不重复工作,否则你就会发现当你想修改一个逻辑是你会需要修改很多地方。
不好的例子
function showDeveloperList($developers) {
foreach ($developers as $developer) {
$expectedSalary = $developer->calculateExpectedSalary();
$experience = $developer->getExperience();
$githubLink = $developer->getGithubLink();
$data = [
$expectedSalary,
$experience,
$githubLink
];
render($data);
}
}
function showManaerList($managers) {
foreach ($managers as $manager) {
$expectedSalary = $manager->calculateExpectedSalary();
$experience = $manager->getExperience();
$githubLink = $manager->getGithubLink();
$data = [
$expectedSalary,
$experience,
$githubLink
];
render($data);
}
}
好的例子
function showist($employees) {
foreach ($employees as $employee) {
$expectedSalary = $employee->calculateExpectedSalary();
$experience = $employee->getExperience();
$githubLink = $employee->getGithubLink();
$data = [
$expectedSalary,
$experience,
$githubLink
];
render($data);
}
}
非常好的例子
精简代码会更好。
function showList($employees) {
foreach ($employees as $employee) {
render([
$employee->calculateExpectedSalary(),
$employee->getExperience(),
$employee->getGithubLink()
]);
}
}