zoukankan      html  css  js  c++  java
  • PHP代码简洁之道——SOLID原则

    • S: 单一职责原则 (SRP)
    • O: 开闭原则 (OCP)
    • L: 里氏替换原则 (LSP)
    • I: 接口隔离原则 (ISP)
    • D: 依赖反转原则 (DIP)

    单一职责原则 Single Responsibility Principle (SRP)

    "修改一个类应该只为一个理由"。人们总是易于用一堆方法塞满一个类,如同我们在飞机上只能携带一个行李箱(把所有的东西都塞到箱子里)。这样做的问题是:从概念上这样的类不是高内聚的,并且留下了很多理由去修改它。将你需要修改类的次数降低到最小很重要。这是因为,当有很多方法在类中时,修改其中一处,你很难知晓在代码库中哪些依赖的模块会被影响到。

    Bad:

     1 class UserSettings
     2 {
     3     private $user;
     4 
     5     public function __construct($user)
     6     {
     7         $this->user = $user;
     8     }
     9 
    10     public function changeSettings($settings)
    11     {
    12         if ($this->verifyCredentials()) {
    13             // ...
    14         }
    15     }
    16 
    17     private function verifyCredentials()
    18     {
    19         // ...
    20     }
    21 }

    Good:

     1 class UserAuth 
     2 {
     3     private $user;
     4 
     5     public function __construct($user)
     6     {
     7         $this->user = $user;
     8     }
     9     
    10     public function verifyCredentials()
    11     {
    12         // ...
    13     }
    14 }
    15 
    16 class UserSettings 
    17 {
    18     private $user;
    19     private $auth;
    20 
    21     public function __construct($user) 
    22     {
    23         $this->user = $user;
    24         $this->auth = new UserAuth($user);
    25     }
    26 
    27     public function changeSettings($settings)
    28     {
    29         if ($this->auth->verifyCredentials()) {
    30             // ...
    31         }
    32     }
    33 }

    开闭原则 Open/Closed Principle (OCP)

    正如Bertrand Meyer所述,"软件的实体(类, 模块, 函数,等)应该对扩展开放,对修改关闭。"这个原则是在说明应该允许用户在不改变已有代码的情况下增加新的功能。

    Bad:

     1 abstract class Adapter
     2 {
     3     protected $name;
     4 
     5     public function getName()
     6     {
     7         return $this->name;
     8     }
     9 }
    10 
    11 class AjaxAdapter extends Adapter
    12 {
    13     public function __construct()
    14     {
    15         parent::__construct();
    16 
    17         $this->name = 'ajaxAdapter';
    18     }
    19 }
    20 
    21 class NodeAdapter extends Adapter
    22 {
    23     public function __construct()
    24     {
    25         parent::__construct();
    26 
    27         $this->name = 'nodeAdapter';
    28     }
    29 }
    30 
    31 class HttpRequester
    32 {
    33     private $adapter;
    34 
    35     public function __construct($adapter)
    36     {
    37         $this->adapter = $adapter;
    38     }
    39 
    40     public function fetch($url)
    41     {
    42         $adapterName = $this->adapter->getName();
    43 
    44         if ($adapterName === 'ajaxAdapter') {
    45             return $this->makeAjaxCall($url);
    46         } elseif ($adapterName === 'httpNodeAdapter') {
    47             return $this->makeHttpCall($url);
    48         }
    49     }
    50 
    51     private function makeAjaxCall($url)
    52     {
    53         // request and return promise
    54     }
    55 
    56     private function makeHttpCall($url)
    57     {
    58         // request and return promise
    59     }
    60 }

    在上面的代码中,对于HttpRequester类中的fetch方法,如果我新增了一个新的xxxAdapter类并且要在fetch方法中用到的话,就需要在HttpRequester类中去修改类(如加上一个elseif 判断),而通过下面的代码,就可很好的解决这个问题。下面代码很好的说明了如何在不改变原有代码的情况下增加新功能。

    Good:

     1 interface Adapter
     2 {
     3     public function request($url);
     4 }
     5 
     6 class AjaxAdapter implements Adapter
     7 {
     8     public function request($url)
     9     {
    10         // request and return promise
    11     }
    12 }
    13 
    14 class NodeAdapter implements Adapter
    15 {
    16     public function request($url)
    17     {
    18         // request and return promise
    19     }
    20 }
    21 
    22 class HttpRequester
    23 {
    24     private $adapter;
    25 
    26     public function __construct(Adapter $adapter)
    27     {
    28         $this->adapter = $adapter;
    29     }
    30 
    31     public function fetch($url)
    32     {
    33         return $this->adapter->request($url);
    34     }
    35 }

    里氏替换原则 Liskov Substitution Principle (LSP)

    对这个概念最好的解释是:如果你有一个父类和一个子类,在不改变原有结果正确性的前提下父类和子类可以互换。这个听起来让人有些迷惑,所以让我们来看一个经典的正方形-长方形的例子。从数学上讲,正方形是一种长方形,但是当你的模型通过继承使用了"is-a"的关系时,就不对了。

    Bad:

     1 class Rectangle
     2 {
     3     protected $width = 0;
     4     protected $height = 0;
     5 
     6     public function render($area)
     7     {
     8         // ...
     9     }
    10 
    11     public function setWidth($width)
    12     {
    13         $this->width = $width;
    14     }
    15 
    16     public function setHeight($height)
    17     {
    18         $this->height = $height;
    19     }
    20 
    21     public function getArea()
    22     {
    23         return $this->width * $this->height;
    24     }
    25 }
    26 
    27 class Square extends Rectangle
    28 {
    29     public function setWidth($width)
    30     {
    31         $this->width = $this->height = $width;
    32     }
    33 
    34     public function setHeight(height)
    35     {
    36         $this->width = $this->height = $height;
    37     }
    38 }
    39 
    40 function renderLargeRectangles($rectangles)
    41 {
    42     foreach ($rectangles as $rectangle) {
    43         $rectangle->setWidth(4);
    44         $rectangle->setHeight(5);
    45         $area = $rectangle->getArea(); // BAD: Will return 25 for Square. Should be 20.
    46         $rectangle->render($area);
    47     }
    48 }
    49 
    50 $rectangles = [new Rectangle(), new Rectangle(), new Square()];
    51 renderLargeRectangles($rectangles);

    Good:

     1 abstract class Shape
     2 {
     3     protected $width = 0;
     4     protected $height = 0;
     5 
     6     abstract public function getArea();
     7 
     8     public function render($area)
     9     {
    10         // ...
    11     }
    12 }
    13 
    14 class Rectangle extends Shape
    15 {
    16     public function setWidth($width)
    17     {
    18         $this->width = $width;
    19     }
    20 
    21     public function setHeight($height)
    22     {
    23         $this->height = $height;
    24     }
    25 
    26     public function getArea()
    27     {
    28         return $this->width * $this->height;
    29     }
    30 }
    31 
    32 class Square extends Shape
    33 {
    34     private $length = 0;
    35 
    36     public function setLength($length)
    37     {
    38         $this->length = $length;
    39     }
    40 
    41     public function getArea()
    42     {
    43         return pow($this->length, 2);
    44     }
    45 }
    46 
    47 function renderLargeRectangles($rectangles)
    48 {
    49     foreach ($rectangles as $rectangle) {
    50         if ($rectangle instanceof Square) {
    51             $rectangle->setLength(5);
    52         } elseif ($rectangle instanceof Rectangle) {
    53             $rectangle->setWidth(4);
    54             $rectangle->setHeight(5);
    55         }
    56 
    57         $area = $rectangle->getArea(); 
    58         $rectangle->render($area);
    59     }
    60 }
    61 
    62 $shapes = [new Rectangle(), new Rectangle(), new Square()];
    63 renderLargeRectangles($shapes);

    接口隔离原则

    接口隔离原则:"客户端不应该被强制去实现于它不需要的接口"。

    有一个清晰的例子来说明示范这条原则。当一个类需要一个大量的设置项,为了方便不会要求客户端去设置大量的选项,因为在通常他们不需要所有的设置项。使设置项可选有助于我们避免产生"胖接口"

    Bad:

     1 interface Employee
     2 {
     3     public function work();
     4 
     5     public function eat();
     6 }
     7 
     8 class Human implements Employee
     9 {
    10     public function work()
    11     {
    12         // ....working
    13     }
    14 
    15     public function eat()
    16     {
    17         // ...... eating in lunch break
    18     }
    19 }
    20 
    21 class Robot implements Employee
    22 {
    23     public function work()
    24     {
    25         //.... working much more
    26     }
    27 
    28     public function eat()
    29     {
    30         //.... robot can't eat, but it must implement this method
    31     }
    32 }

    上面的代码中,Robot类并不需要eat()这个方法,但是实现了Emplyee接口,于是只能实现所有的方法了,这使得Robot实现了它并不需要的方法。所以在这里应该对Emplyee接口进行拆分,正确的代码如下:

    Good:

     1 interface Workable
     2 {
     3     public function work();
     4 }
     5 
     6 interface Feedable
     7 {
     8     public function eat();
     9 }
    10 
    11 interface Employee extends Feedable, Workable
    12 {
    13 }
    14 
    15 class Human implements Employee
    16 {
    17     public function work()
    18     {
    19         // ....working
    20     }
    21 
    22     public function eat()
    23     {
    24         //.... eating in lunch break
    25     }
    26 }
    27 
    28 // robot can only work
    29 class Robot implements Workable
    30 {
    31     public function work()
    32     {
    33         // ....working
    34     }
    35 }

    依赖反转原则 Dependency Inversion Principle (DIP)

    这条原则说明两个基本的要点:

    • 高阶的模块不应该依赖低阶的模块,它们都应该依赖于抽象
    • 抽象不应该依赖于实现,实现应该依赖于抽象

    问题由来:

      类A直接依赖类B,假如要将类A改为依赖类C,则必须通过修改类A的代码来达成。这种场景下,类A一般是高层模块,负责复杂的业务逻辑;类B和类C是低层模块,负责基本的原子操作;假如修改类A,会给程序带来不必要的风险。

    解决方案:

      将类A修改为依赖接口I,类B和类C各自实现接口I,类A通过接口I间接与类B或者类C发生联系,则会大大降低修改类A的几率。

    依赖倒置原则基于这样一个事实:相对于细节的多变性,抽象的东西要稳定的多。以抽象为基础搭建起来的架构比以细节为基础搭建起来的架构要稳定的多。在java中,抽象指的是接口或者抽象类,细节就是具体的实现类,使用接口或者抽象类的目的是制定好规范和契约,而不去涉及任何具体的操作,把展现细节的任务交给他们的实现类去完成。

    Bad:

     1 class Employee
     2 {
     3     public function work()
     4     {
     5         // ....working
     6     }
     7 }
     8 
     9 class Robot extends Employee
    10 {
    11     public function work()
    12     {
    13         //.... working much more
    14     }
    15 }
    16 
    17 class Manager
    18 {
    19     private $employee;
    20 
    21     public function __construct(Employee $employee)
    22     {
    23         $this->employee = $employee;
    24     }
    25 
    26     public function manage()
    27     {
    28         $this->employee->work();
    29     }
    30 }

    Good:

     1 interface Employee
     2 {
     3     public function work();
     4 }
     5 
     6 class Human implements Employee
     7 {
     8     public function work()
     9     {
    10         // ....working
    11     }
    12 }
    13 
    14 class Robot implements Employee
    15 {
    16     public function work()
    17     {
    18         //.... working much more
    19     }
    20 }
    21 
    22 class Manager
    23 {
    24     private $employee;
    25 
    26     public function __construct(Employee $employee)
    27     {
    28         $this->employee = $employee;
    29     }
    30 
    31     public function manage()
    32     {
    33         $this->employee->work();
    34     }
    35 }

    别写重复代码 (DRY)

    这条原则大家应该都是比较熟悉了。

    尽你最大的努力去避免复制代码,它是一种非常糟糕的行为,复制代码通常意味着当你需要变更一些逻辑时,你需要修改不止一处。

    Bad:

     1 function showDeveloperList($developers)
     2 {
     3     foreach ($developers as $developer) {
     4         $expectedSalary = $developer->calculateExpectedSalary();
     5         $experience = $developer->getExperience();
     6         $githubLink = $developer->getGithubLink();
     7         $data = [
     8             $expectedSalary,
     9             $experience,
    10             $githubLink
    11         ];
    12 
    13         render($data);
    14     }
    15 }
    16 
    17 function showManagerList($managers)
    18 {
    19     foreach ($managers as $manager) {
    20         $expectedSalary = $manager->calculateExpectedSalary();
    21         $experience = $manager->getExperience();
    22         $githubLink = $manager->getGithubLink();
    23         $data = [
    24             $expectedSalary,
    25             $experience,
    26             $githubLink
    27         ];
    28 
    29         render($data);
    30     }
    31 }

    Good:

     1 function showList($employees)
     2 {
     3     foreach ($employees as $employee) {
     4         $expectedSalary = $employee->calculateExpectedSalary();
     5         $experience = $employee->getExperience();
     6         $githubLink = $employee->getGithubLink();
     7         $data = [
     8             $expectedSalary,
     9             $experience,
    10             $githubLink
    11         ];
    12 
    13         render($data);
    14     }
    15 }

    Very good:

    function showList($employees)
    {
        foreach ($employees as $employee) {
            render([
                $employee->calculateExpectedSalary(),
                $employee->getExperience(),
                $employee->getGithubLink()
            ]);
        }
    }

    后记:虽然OOP设计需要遵守如上原则,不过实际的代码设计一定要简单、简单、简单。在实际编码中要根据情况进行取舍,一味遵守原则,而不注重实际情况的话,可能会让你的代码变的难以理解!

  • 相关阅读:
    【Maven实战技巧】「插件使用专题」MavenArchetype插件创建自定义maven项目骨架
    ☕【权限设计系列】「认证授权专题」微服务架构的登陆认证问题
    ☕【Java深层系列】「技术盲区」让我们一起探索一下Netty(Java)底层的“零拷贝ZeroCopy”技术(上)
    【Redis集群原理专题】分析一下相关的Redis集群模式下的脑裂问题!
    🏆【Alibaba中间件技术系列】「RocketMQ技术专题」Broker配置介绍及发送流程、异常(XX Busy)问题分析
    ☕【难点攻克技术系列】「海量数据计算系列」如何使用BitMap在海量数据中对相应的进行去重、查找和排序
    盘点 2021|「避坑宝典」为大家分享一下笔者在 2021 年所遇到“匪夷所思”的 Bug 趣事(上)
    🏆【Alibaba中间件技术系列】「RocketMQ技术专题」小白专区之领略一下RocketMQ基础之最!
    🍃【Spring专题】「原理系列」SpringMVC的运行工作原理(补充修订)
    🏆【Alibaba中间件技术系列】「EasyExcel实战案例」实战研究一下EasyExcel如何从指定文件位置进行读取数据
  • 原文地址:https://www.cnblogs.com/zgxblog/p/10614044.html
Copyright © 2011-2022 走看看