您的位置:首页 >  新闻中心 > 行业动态
  行业动态
 

PHP代码简洁之道——SOLID原则

来源:原创    时间:2017-10-24    浏览:0 次

SOLID 是Michael Feathers引荐的便于回忆的首字母简写,它代表了Robert Martin命名的最重要的五个面临目标编码规划准则
S: 单一责任准则 (SRP)
O: 开闭准则 (OCP)
L: 里氏替换准则 (LSP)
I: 接口阻隔准则 (ISP)
D: 依靠回转准则 (DIP)
单一责任准则 Single Responsibility Principle (SRP)
"修正一个类应该只为一个理由"。人们总是易于用一堆办法塞满一个类,好像我们在飞机上只能带着一个行李箱(把一切的东西都塞到箱子里)。这样做的问题是:从概念上这样的类不是高内聚的,而且留下了许多理由去修正它。将你需求修正类的次数降低到最小很重要。这是由于,当有许多办法在类中时,修正其间一处,你很难知晓在代码库中哪些依靠的模块会被影响到。

Bad:
class UserSettings{    
    private $user;    
    public function __construct($user)
    {        
        $this->user = $user;
    }    
    public function changeSettings($settings)
    {        
        if ($this->verifyCredentials()) {           
         // ...
        }
    }    
    private function verifyCredentials()
    {        
    // ...
    }
}
Good:
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()) {            
    // ...
        }
    }
}
开闭准则 Open/Closed Principle (OCP)
正如Bertrand Meyer所述,"软件的实体(类, 模块, 函数,等)应该对扩展敞开,对修正封闭。"这个准则是在阐明应该答应用户在不改动已有代码的状况下添加新的功用。
Bad:
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 判别),而经过下面的代码,就可很好的处理这个问题。下面代码很好的阐明了如安在不改动原有代码的状况下添加新功用。
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);
    }
}
里氏替换准则 Liskov Substitution Principle (LSP)
对这个概念最好的解说是:如果你有一个父类和一个子类,在不改动原有成果正确性的前提下父类和子类能够交换。这个听起来让人有些利诱,所以让我们来看一个经典的正方形-长方形的比如。从数学上讲,正方形是一种长方形,可是当你的模型经过承继运用了"is-a"的联系时,就不对了。
Bad:
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
    }
}
依靠回转准则 Dependency Inversion Principle (DIP)
这条准则阐明两个根本的关键:
高阶的模块不该该依靠低阶的模块,它们都应该依靠于笼统
笼统不该该依靠于完成,完成应该依靠于笼统
这条起先看起来有点不流畅难明,可是如果你运用过php结构(例如 Symfony),你应该见过依靠注入(DI)对这个概念的完成。尽管它们不是彻底相通的概念,依靠倒置准则使高阶模块与低阶模块的完成细节和创立别离。能够运用依靠注入(DI)这种方法来完成它。更多的优点是它使模块之间解耦。耦合会导致你难于重构,它是一种十分糟糕的的开发形式。
Bad:
class Employee{    
    public function work()