资讯专栏INFORMATION COLUMN

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

PrototypeZ / 1261人阅读

摘要:是推荐的便于记忆的首字母简写,它代表了命名的最重要的五个面对对象编码设计原则单一职责原则开闭原则里氏替换原则接口隔离原则依赖反转原则单一职责原则修改一个类应该只为一个理由。别写重复代码这条原则大家应该都是比较熟悉了。

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()
    {
        // ....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设计需要遵守如上原则,不过实际的代码设计一定要简单、简单、简单。在实际编码中要根据情况进行取舍,一味遵守原则,而不注重实际情况的话,可能会让你的代码变的难以理解!

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/31978.html

相关文章

  • PHP代码简洁之道——类和对象部分

    摘要:使用和在中,通过为属性或方法设置和关键字可以实现对属性或方法的可见性控制。你的继承表达了一个对等比如人类是动物的关系,不是包含的关系比如用户具有用户详情你能从基类中复用代码你想通过修改全局类来对所有派生类进行修改。 使用getter和setter 在 PHP 中,通过为属性或方法设置 public, protected 和 private 关键字可以实现对属性或方法的可见性控制。不过,...

    cyixlq 评论0 收藏0
  • PHP简洁之道

    摘要:另外一个严重的打击是当你需要排序测试的时候,在单元测试中这会是一个不小的麻烦。为什么因为每个单元测试都应该依赖于另外一个。对于所有子类来说,在类中修改也是相当危险的。 前言 原文地址:https://github.com/jupeter/cl... 译文地址:https://github.com/nineyang/c...,欢迎star。 变量 使用更有意义和更加直白的命名方式 不...

    lixiang 评论0 收藏0
  • PHP代码简洁之道——函数部分

    摘要:超过三个参数会导致参数之间的组合过多,你必须对每个单独的参数测试大量不同的情况。拆分这些函数,可以让代码可重用性更高且更易测试。 函数参数不要超过两个 限制函数的参数数量是非常重要的,因为它使你的函数更容易测试。超过三个参数会导致参数之间的组合过多,你必须对每个单独的参数测试大量不同的情况。 没有参数是最理想的情况,一个或两个参数是可以接受的,三个以上则是应该避免的。这很重要的。如果你...

    crossoverJie 评论0 收藏0
  • JavaScript 代码简洁之道

    摘要:代码质量与其整洁度成正比。它让你的代码简洁优雅。是几个单词首字母组合而来,分别表示单一功能原则开闭原则里氏替换原则接口隔离原则以及依赖反转原则。开闭原则开指的就是类模块函数都应该具有可扩展性,闭指的是它们不应该被修改。 测试代码质量的唯一方式:别人看你代码时说 f * k 的次数。 代码质量与其整洁度成正比。干净的代码,既在质量上较为可靠,也为后期维护、升级奠定了良好基础。 本文并不是...

    LinkedME2016 评论0 收藏0
  • JavaScript 代码简洁之道

    摘要:代码质量与其整洁度成正比。它让你的代码简洁优雅。是几个单词首字母组合而来,分别表示单一功能原则开闭原则里氏替换原则接口隔离原则以及依赖反转原则。开闭原则开指的就是类模块函数都应该具有可扩展性,闭指的是它们不应该被修改。 测试代码质量的唯一方式:别人看你代码时说 f * k 的次数。 代码质量与其整洁度成正比。干净的代码,既在质量上较为可靠,也为后期维护、升级奠定了良好基础。 本文并不是...

    邹立鹏 评论0 收藏0

发表评论

0条评论

PrototypeZ

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<