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

  • 相关阅读:
    【缺少kubernetes权限】 namespaces "xxx" is forbidden: User "xxx" cannot xxx resource "xxx" in API group "xxx" in the namespace "xxx"
    【kubernetes secret 和 aws ecr helper】kubernetes从docker拉取image,kubernetes docker私服认证(argo docker私服认证),no basic auth credentials错误解决
    win10老提示系统错误,要注销
    win10无法访问局域网共享文件?解决如此简单。。。。。
    filezilla server老提示connect server
    代理ARP
    win10用filezilla server搭建ftp服务器一直无法访问
    华为路由器GRE配置
    spring笔记--使用springAPI以及自定义类 实现AOP的一个例子
    spring笔记--依赖注入之针对不同类型变量的几种注入方式
  • 原文地址:https://www.cnblogs.com/zgxblog/p/10614044.html
Copyright © 2011-2022 走看看