SOLID 是Michael Feathers推荐的便于记忆的首字母简写,它表明了Robert Martin命名的最重要的五个面对对象编码设计原则php
"修改一个类应该只为一个理由"。人们老是易于用一堆方法塞满一个类,如同咱们在飞机上只能携带一个行李箱(把全部的东西都塞到箱子里)。这样作的问题是:从概念上这样的类不是高内聚的,而且留下了不少理由去修改它。将你须要修改类的次数下降到最小很重要。这是由于,当有不少方法在类中时,修改其中一处,你很难知晓在代码库中哪些依赖的模块会被影响到。node
Bad:git
class UserSettings { private $user; public function __construct($user) { $this->user = $user; } public function changeSettings($settings) { if ($this->verifyCredentials()) { // ... } } private function verifyCredentials() { // ... } }
Good:github
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()) { // ... } } }
正如Bertrand Meyer所述,"软件的实体(类, 模块, 函数,等)应该对扩展开放,对修改关闭。"这个原则是在说明应该容许用户在不改变已有代码的状况下增长新的功能。ajax
Bad:promise
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 === 'httpNodeAdapter') { return $this->makeHttpCall($url); } } private function makeAjaxCall($url) { // request and return promise } private function makeHttpCall($url) { // request and return promise } }
在上面的代码中,对于HttpRequester类中的fetch方法,若是我新增了一个新的xxxAdapter类而且要在fetch方法中用到的话,就须要在HttpRequester类中去修改类(如加上一个elseif 判断),而经过下面的代码,就可很好的解决这个问题。下面代码很好的说明了如何在不改变原有代码的状况下增长新功能。php框架
Good:框架
interface Adapter { public function request($url); } class AjaxAdapter implements Adapter { public function request($url) { // request and return promise } } class NodeAdapter implements Adapter { public function 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); } }
对这个概念最好的解释是:若是你有一个父类和一个子类,在不改变原有结果正确性的前提下父类和子类能够互换。这个听起来让人有些迷惑,因此让咱们来看一个经典的正方形-长方形的例子。从数学上讲,正方形是一种长方形,可是当你的模型经过继承使用了"is-a"的关系时,就不对了。函数
Bad:fetch
class Rectangle { protected $width = 0; protected $height = 0; public function render($area) { // ... } 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 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); } } $rectangles = [new Rectangle(), new Rectangle(), new Square()]; renderLargeRectangles($rectangles);
Good:
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 { private $length = 0; public function setLength($length) { $this->length = $length; } public function getArea() { return pow($this->length, 2); } } function renderLargeRectangles($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()]; renderLargeRectangles($shapes);
接口隔离原则:"客户端不该该被强制去实现于它不须要的接口"。
有一个清晰的例子来讲明示范这条原则。当一个类须要一个大量的设置项,为了方便不会要求客户端去设置大量的选项,由于在一般他们不须要全部的设置项。使设置项可选有助于咱们避免产生"胖接口"
Bad:
interface Employee { public function work(); public function eat(); } class Human implements Employee { public function work() { // ....working } public function eat() { // ...... eating in lunch break } } class Robot implements Employee { public function work() { //.... working much more } public function eat() { //.... robot can't eat, but it must implement this method } }
上面的代码中,Robot类并不须要eat()这个方法,可是实现了Emplyee接口,因而只能实现全部的方法了,这使得Robot实现了它并不须要的方法。因此在这里应该对Emplyee接口进行拆分,正确的代码以下:
Good:
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() { // ....working } }
这条原则说明两个基本的要点:
这条起初看起来有点晦涩难懂,可是若是你使用过php框架(例如 Symfony),你应该见过依赖注入(DI)对这个概念的实现。虽然它们不是彻底相通的概念,依赖倒置原则使高阶模块与低阶模块的实现细节和建立分离。可使用依赖注入(DI)这种方式来实现它。更多的好处是它使模块之间解耦。耦合会致使你难于重构,它是一种很是糟糕的的开发模式。
Bad:
class Employee { public function work() { // ....working } } 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(); } }
Good:
interface Employee { public function work(); } class Human implements Employee { public function work() { // ....working } } class Robot implements 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(); } }
这条原则你们应该都是比较熟悉了。
尽你最大的努力去避免复制代码,它是一种很是糟糕的行为,复制代码一般意味着当你须要变动一些逻辑时,你须要修改不止一处。
Bad:
function showDeveloperList($developers) { foreach ($developers as $developer) { $expectedSalary = $developer->calculateExpectedSalary(); $experience = $developer->getExperience(); $githubLink = $developer->getGithubLink(); $data = [ $expectedSalary, $experience, $githubLink ]; render($data); } } function showManagerList($managers) { foreach ($managers as $manager) { $expectedSalary = $manager->calculateExpectedSalary(); $experience = $manager->getExperience(); $githubLink = $manager->getGithubLink(); $data = [ $expectedSalary, $experience, $githubLink ]; render($data); } }
Good:
function showList($employees) { foreach ($employees as $employee) { $expectedSalary = $employee->calculateExpectedSalary(); $experience = $employee->getExperience(); $githubLink = $employee->getGithubLink(); $data = [ $expectedSalary, $experience, $githubLink ]; render($data); } }
Very good:
function showList($employees) { foreach ($employees as $employee) { render([ $employee->calculateExpectedSalary(), $employee->getExperience(), $employee->getGithubLink() ]); } }
后记:虽然OOP设计须要遵照如上原则,不过实际的代码设计必定要简单、简单、简单。在实际编码中要根据状况进行取舍,一味遵照原则,而不注重实际状况的话,可能会让你的代码变的难以理解!