SOLID 设计原则

未匹配的标注

SOLID 是 Michael Feathers 为 Robert Martin 命名的前五个原则引入的助记首字母缩写词,表示面向对象编程和设计的五个基本原则。

单一职责原则 (SRP)

正如 Clean Code 中所述,「更改一个类的理由应该只有一个」。把一个有很多功能的类塞进一个包里是很诱人的,例如当您在航班上只能带一个手提箱时。这样做的问题是你的类在概念上不会有凝聚力,它会给它很多改变的理由。
尽量减少需要更改类的次数很重要。
这很重要,因为如果一个类中有太多功能并且您修改了其中的一部分,则可能很难预估这将如何影响代码库中的其他依赖模块。

Bad:

class UserSettings
{
    private $user;

    public function __construct(User $user)
    {
        $this->user = $user;
    }

    public function changeSettings(array $settings): void
    {
        if ($this->verifyCredentials()) {
            // ...
        }
    }

    private function verifyCredentials(): bool
    {
        // ...
    }
}

Good:

class UserAuth
{
    private $user;

    public function __construct(User $user)
    {
        $this->user = $user;
    }

    public function verifyCredentials(): bool
    {
        // ...
    }
}

class UserSettings
{
    private $user;

    private $auth;

    public function __construct(User $user)
    {
        $this->user = $user;
        $this->auth = new UserAuth($user);
    }

    public function changeSettings(array $settings): void
    {
        if ($this->auth->verifyCredentials()) {
            // ...
        }
    }
}

开/闭原则 (OCP)

正如 Bertrand Meyer 所说,「软件实体(类、模块、函数、
等等。) 应该对扩展开放,但对修改关闭。」那是什么意思呢? 该原则基本上表明您应该允许用户在不更改现有代码的情况下添加新功能。

Bad:

abstract class Adapter
{
    protected $name;

    public function getName(): string
    {
        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 $adapter)
    {
        $this->adapter = $adapter;
    }

    public function fetch(string $url): Promise
    {
        $adapterName = $this->adapter->getName();

        if ($adapterName === 'ajaxAdapter') {
            return $this->makeAjaxCall($url);
        } elseif ($adapterName === 'httpNodeAdapter') {
            return $this->makeHttpCall($url);
        }
    }

    private function makeAjaxCall(string $url): Promise
    {
        // request and return promise
    }

    private function makeHttpCall(string $url): Promise
    {
        // request and return promise
    }
}

Good:

interface Adapter
{
    public function request(string $url): Promise;
}

class AjaxAdapter implements Adapter
{
    public function request(string $url): Promise
    {
        // request and return promise
    }
}

class NodeAdapter implements Adapter
{
    public function request(string $url): Promise
    {
        // request and return promise
    }
}

class HttpRequester
{
    private $adapter;

    public function __construct(Adapter $adapter)
    {
        $this->adapter = $adapter;
    }

    public function fetch(string $url): Promise
    {
        return $this->adapter->request($url);
    }
}

里氏替换原则 (LSP)

对于一个非常简单的概念来说,这是一个可怕的术语。它的含义是「如果 S 是 T 的子类型,则类型 T 的对象可以被类型 S 的对象替换(即,类型 S 的对象可以替换类型 T 的对象)而不改变该程序的任何所需属性 (正确性、执行的任务等)。」 这是一个更可怕的定义。

对此最好的解释是,如果你有一个父类和一个子类,那么基类和子类可以互换使用而不会得到错误的结果。 这可能仍然令人困惑,所以让我们看一下经典的 Square-Rectangle 示例。 从数学上讲,正方形是长方形,但如果您通过继承使用「is-a」关系对其进行建模,您很快就会遇到麻烦。

Bad:

class Rectangle
{
    protected $width = 0;

    protected $height = 0;

    public function setWidth(int $width): void
    {
        $this->width = $width;
    }

    public function setHeight(int $height): void
    {
        $this->height = $height;
    }

    public function getArea(): int
    {
        return $this->width * $this->height;
    }
}

class Square extends Rectangle
{
    public function setWidth(int $width): void
    {
        $this->width = $this->height = $width;
    }

    public function setHeight(int $height): void
    {
        $this->width = $this->height = $height;
    }
}

function printArea(Rectangle $rectangle): void
{
    $rectangle->setWidth(4);
    $rectangle->setHeight(5);

    // BAD: Will return 25 for Square. Should be 20.
    echo sprintf('%s has area %d.', get_class($rectangle), $rectangle->getArea()) . PHP_EOL;
}

$rectangles = [new Rectangle(), new Square()];

foreach ($rectangles as $rectangle) {
    printArea($rectangle);
}

Good:

最好的方法是将四边形分开并为两种形状分配更通用的子类型。

尽管正方形和长方形有明显的相似性,但是它们是不同的。
正方形和菱形有很多相似之处,而矩形和平行四边形也是这样,但是它们不是子类型。
正方形,长方形,菱形和平行四边形都是独立的形状,它们有自己的属性,虽然很相似。

interface Shape
{
    public function getArea(): int;
}

class Rectangle implements Shape
{
    private $width = 0;
    private $height = 0;

    public function __construct(int $width, int $height)
    {
        $this->width = $width;
        $this->height = $height;
    }

    public function getArea(): int
    {
        return $this->width * $this->height;
    }
}

class Square implements Shape
{
    private $length = 0;

    public function __construct(int $length)
    {
        $this->length = $length;
    }

    public function getArea(): int
    {
        return $this->length ** 2;
    }
}

function printArea(Shape $shape): void
{
    echo sprintf('%s has area %d.', get_class($shape), $shape->getArea()).PHP_EOL;
}

$shapes = [new Rectangle(4, 5), new Square(5)];

foreach ($shapes as $shape) {
    printArea($shape);
}

接口隔离原则 (ISP)

ISP 声明 “不应强迫客户端依赖于它们不使用的接口。”

一个很好的例子就说明了这个原则,它适用于需要大量设置对象的类。不要求客户端去设置大量选项是有益的,因为很多时候他们不需要所有的设置。 使其可选有助于防止 “肥胖的接口”。

坏的:

interface Employee
{
    public function work(): void;

    public function eat(): void;
}

class HumanEmployee implements Employee
{
    public function work(): void
    {
        // ....working
    }

    public function eat(): void
    {
        // ...... eating in lunch break
    }
}

class RobotEmployee implements Employee
{
    public function work(): void
    {
        //.... working much more
    }

    public function eat(): void
    {
        //.... robot can't eat, but it must implement this method
    }
}

好的:

不是每个工人都是员工,但是每个员工都是工人。

interface Workable
{
    public function work(): void;
}

interface Feedable
{
    public function eat(): void;
}

interface Employee extends Feedable, Workable
{
}

class HumanEmployee implements Employee
{
    public function work(): void
    {
        // ....工作中
    }

    public function eat(): void
    {
        //.... 午休时间吃饭
    }
}

// 机器人只能工作
class RobotEmployee implements Workable
{
    public function work(): void
    {
        // ....工作中
    }
}

依赖反转原则 (DIP)

这个原则声明了两件基本的事情:

  1. 高层模块不应依赖于低层模块。两者都应依赖于抽象。
  2. 抽象不应依赖于具体。 具体应依赖于抽象。

一开始这很难理解,但如果您使用过 PHP 框架 (如 Symfony),您已经看到了以依赖注入 (DI) 的形式对这一原则的实现。虽然它们不是完全相同的概念,DIP 阻止了高层模块了解低层模块的实现和设置。
它可以通过 DI 实现这一点。这其中最大的好处是,它减少了模块之间的耦合。耦合是一种非常糟糕的开发模式,因为它使代码难以重构。

坏的:

class Employee
{
    public function work(): void
    {
        // ....working
    }
}

class Robot extends Employee
{
    public function work(): void
    {
        //.... working much more
    }
}

class Manager
{
    private $employee;

    public function __construct(Employee $employee)
    {
        $this->employee = $employee;
    }

    public function manage(): void
    {
        $this->employee->work();
    }
}

好的:

interface Employee
{
    public function work(): void;
}

class Human implements Employee
{
    public function work(): void
    {
        // ....working
    }
}

class Robot implements Employee
{
    public function work(): void
    {
        //.... working much more
    }
}

class Manager
{
    private $employee;

    public function __construct(Employee $employee)
    {
        $this->employee = $employee;
    }

    public function manage(): void
    {
        $this->employee->work();
    }
}

本文章首发在 LearnKu.com 网站上。

本译文仅用于学习和交流目的,转载请务必注明文章译者、出处、和本文链接
我们的翻译工作遵照 CC 协议,如果我们的工作有侵犯到您的权益,请及时联系我们。

原文地址:https://learnku.com/docs/clean-code-php/...

译文地址:https://learnku.com/docs/clean-code-php/...

上一篇 下一篇
《L03 构架 API 服务器》
你将学到如 RESTFul 设计风格、PostMan 的使用、OAuth 流程,JWT 概念及使用 和 API 开发相关的进阶知识。
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
贡献者:3
讨论数量: 0
发起讨论 只看当前版本


暂无话题~