PHP代碼簡潔之道——SOLID原則

SOLID 是Michael Feathers推薦的便於記憶的首字母簡寫,它表明了Robert Martin命名的最重要的五個面對對象編碼設計原則php

  • S: 單一職責原則 (SRP)
  • O: 開閉原則 (OCP)
  • L: 里氏替換原則 (LSP)
  • I: 接口隔離原則 (ISP)
  • D: 依賴反轉原則 (DIP)

單一職責原則 Single Responsibility Principle (SRP)

"修改一個類應該只爲一個理由"。人們老是易於用一堆方法塞滿一個類,如同咱們在飛機上只能攜帶一個行李箱(把全部的東西都塞到箱子裏)。這樣作的問題是:從概念上這樣的類不是高內聚的,而且留下了不少理由去修改它。將你須要修改類的次數下降到最小很重要。這是由於,當有不少方法在類中時,修改其中一處,你很難知曉在代碼庫中哪些依賴的模塊會被影響到。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()) {
            // ...
        }
    }
}

開閉原則 Open/Closed Principle (OCP)

正如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);
    }
}

里氏替換原則 Liskov Substitution Principle (LSP)

對這個概念最好的解釋是:若是你有一個父類和一個子類,在不改變原有結果正確性的前提下父類和子類能夠互換。這個聽起來讓人有些迷惑,因此讓咱們來看一個經典的正方形-長方形的例子。從數學上講,正方形是一種長方形,可是當你的模型經過繼承使用了"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
    }
}

依賴反轉原則 Dependency Inversion Principle (DIP)

這條原則說明兩個基本的要點:

  • 高階的模塊不該該依賴低階的模塊,它們都應該依賴於抽象
  • 抽象不該該依賴於實現,實現應該依賴於抽象

這條起初看起來有點晦澀難懂,可是若是你使用過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();
    }
}

別寫重複代碼 (DRY)

這條原則你們應該都是比較熟悉了。

盡你最大的努力去避免複製代碼,它是一種很是糟糕的行爲,複製代碼一般意味着當你須要變動一些邏輯時,你須要修改不止一處。

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設計須要遵照如上原則,不過實際的代碼設計必定要簡單、簡單、簡單。在實際編碼中要根據狀況進行取捨,一味遵照原則,而不注重實際狀況的話,可能會讓你的代碼變的難以理解!

相關文章
相關標籤/搜索