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

简介:

PHP代码简洁之道SOLID原则

SOLID 是Michael Feathers推荐的便于记忆的首字母简写,它代表了Robert Martin命名的最重要的五个面对对象编码设计原则:

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

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

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

Bad:

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

Good:

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

开闭原则 Open/Closed Principle (OCP)

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

Bad:

 
  1. abstract class Adapter{     
  2. protected $name;     
  3. public function getName(){         
  4.     return $this->name
  5. class AjaxAdapter extends Adapter{     
  6. public function __construct(){      
  7.       parent::__construct();         
  8.       $this->name = 'ajaxAdapter'
  9.  } 
  10. class NodeAdapter extends Adapter{     
  11.     public function __construct(){    
  12.         parent::__construct();         
  13.         $this->name = 'nodeAdapter'
  14.     } 
  15.     class HttpRequester{     
  16.     private $adapter;     
  17.     public function __construct($adapter) 
  18.     {         
  19.         $this->adapter = $adapter; 
  20.     }     
  21.     public function fetch($url) 
  22.     { 
  23.         $adapterName = $this->adapter->getName();         
  24.     if ($adapterName === 'ajaxAdapter') {             
  25.         return $this->makeAjaxCall($url); 
  26.         }  
  27.     elseif ($adapterName === 'httpNodeAdapter') {             
  28.         return $this->makeHttpCall($url); 
  29.         } 
  30.     }     
  31.     private function makeAjaxCall($url) 
  32.     {        // request and return promise 
  33.     }     
  34.     private function makeHttpCall($url) 
  35.     {        // request and return promise 
  36.     } 
  37. }  

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

Good:

 
  1. interface Adapter{     
  2.     public function request($url); 
  3.     class AjaxAdapter implements Adapter{     
  4.     public function request($url) 
  5.     {        // request and return promise 
  6.     } 
  7. class NodeAdapter implements Adapter{     
  8.     public function request($url) 
  9.     {        // request and return promise 
  10.     } 
  11.     class HttpRequester{     
  12.     private $adapter;     
  13.     public function __construct(Adapter $adapter) 
  14.     {        $this->adapter = $adapter; 
  15.     }     
  16.     public function fetch($url) 
  17.     {        return $this->adapter->request($url); 
  18.     } 
  19. }  

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

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

Bad:

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

Good:

 
  1. abstract class Shape{     
  2.     protected $width = 0;     
  3.     protected $height = 0;     
  4.     abstract public function getArea();     
  5.     public function render($area)    {        // ... 
  6.     } 
  7. class Rectangle extends Shape{     
  8.     public function setWidth($width) 
  9.     {        $this->width = $width; 
  10.     }     
  11.     public function setHeight($height) 
  12.     {        $this->height = $height; 
  13.     }     
  14.     public function getArea() 
  15.     {        return $this->width * $this->height; 
  16.     } 
  17. class Square extends Shape{     
  18.     private $length = 0;     
  19.     public function setLength($length) 
  20.     {        $this->length = $length; 
  21.     }     
  22.     public function getArea() 
  23.     {        return pow($this->length, 2); 
  24.     } 
  25. function renderLargeRectangles($rectangles){     
  26. foreach ($rectangles as $rectangle) {         
  27. if ($rectangle instanceof Square) { 
  28.             $rectangle->setLength(5); 
  29.         } elseif ($rectangle instanceof Rectangle) { 
  30.             $rectangle->setWidth(4); 
  31.             $rectangle->setHeight(5); 
  32.         } 
  33.  
  34.         $area = $rectangle->getArea();  
  35.         $rectangle->render($area); 
  36.     } 
  37.  
  38. $shapes = [new Rectangle(), new Rectangle(), new Square()]; 
  39. renderLargeRectangles($shapes);  

接口隔离原则

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

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

Bad:

 
  1. interface Employee{     
  2.     public function work();     
  3.     public function eat(); 
  4. class Human implements Employee{     
  5.     public function work() 
  6.     {        // ....working 
  7.     }     
  8.     public function eat() 
  9.     {        // ...... eating in lunch break 
  10.     } 
  11. }class Robot implements Employee{     
  12.     public function work() 
  13.     {        //.... working much more 
  14.     }     
  15.     public function eat() 
  16.     {        //.... robot can't eat, but it must implement this method 
  17.     } 
  18. }  

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

Good:

 
  1. interface Workable{     
  2.     public function work(); 
  3. interface Feedable{     
  4.     public function eat(); 
  5. interface Employee extends Feedable, Workable{ 
  6. class Human implements Employee{     
  7.     public function work() 
  8.     {        // ....working 
  9.     }     
  10.     public function eat() 
  11.     {        //.... eating in lunch break 
  12.     } 
  13. }// robot can only work 
  14.  
  15. class Robot implements Workable{     
  16.     public function work() 
  17.     {        // ....working 
  18.     } 
  19. }  

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

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

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

这条起初看起来有点晦涩难懂,但是如果你使用过php框架(例如 Symfony),你应该见过依赖注入(DI)对这个概念的实现。虽然它们不是完全相通的概念,依赖倒置原则使高阶模块与低阶模块的实现细节和创建分离。可以使用依赖注入(DI)这种方式来实现它。更多的好处是它使模块之间解耦。耦合会导致你难于重构,它是一种非常糟糕的的开发模式。

Bad:

 
  1. class Employee{     
  2.     public function work() 
  3.     {        // ....working 
  4.     } 
  5. class Robot extends Employee{     
  6.     public function work()    {        //.... working much more 
  7.     } 
  8. class Manager{     
  9.     private $employee;    
  10.     public function __construct(Employee $employee) 
  11.     {        $this->employee = $employee; 
  12.     }    public function manage() 
  13.     {        $this->employee->work(); 
  14.     } 
  15. }  

Good:

 
  1. interface Employee{    
  2.  public function work(); 
  3.  class Human implements Employee{    
  4. public function work() 
  5.     {        // ....working 
  6.     } 
  7. class Robot implements Employee{     
  8. public function work() 
  9.     {        //.... working much more 
  10.     } 
  11. class Manager{     
  12. private $employee;     
  13. public function __construct(Employee $employee) 
  14.     {        $this->employee = $employee; 
  15.     }    public function manage() 
  16.     {        $this->employee->work(); 
  17.     } 
  18. }  

别写重复代码 (DRY)

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

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

Bad:

 
  1. function showDeveloperList($developers){     
  2. foreach ($developers as $developer) { 
  3.         $expectedSalary =  
  4. $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. function showManagerList($managers){     
  16. foreach ($managers as $manager) { 
  17.         $expectedSalary =  
  18. $manager->calculateExpectedSalary(); 
  19.         $experience = $manager->getExperience(); 
  20.         $githubLink = $manager->getGithubLink(); 
  21.         $data = [ 
  22.             $expectedSalary, 
  23.             $experience, 
  24.             $githubLink 
  25.         ]; 
  26.  
  27.         render($data); 
  28.     } 
  29. }  

Good:

 
  1. function showList($employees){     
  2. foreach ($employees as $employee) { 
  3.         $expectedSalary =  
  4. $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:

 
  1. function showList($employees){    foreach ($employees as $employee) { 
  2.         render([ 
  3.             $employee->calculateExpectedSalary(), 
  4.             $employee->getExperience(), 
  5.             $employee->getGithubLink() 
  6.         ]); 
  7.     } 
  8. }  

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


本文作者:佚名

来源:51CTO

相关文章
|
15天前
|
安全 关系型数据库 MySQL
PHP与MySQL交互:从入门到实践
【9月更文挑战第20天】在数字时代的浪潮中,掌握PHP与MySQL的互动成为了开发动态网站和应用程序的关键。本文将通过简明的语言和实例,引导你理解PHP如何与MySQL数据库进行对话,开启你的编程之旅。我们将从连接数据库开始,逐步深入到执行查询、处理结果,以及应对常见的挑战。无论你是初学者还是希望提升技能的开发者,这篇文章都将为你提供实用的知识和技巧。让我们一起探索PHP与MySQL交互的世界,解锁数据的力量!
|
关系型数据库 MySQL PHP
PHP 原生操作 Mysql
PHP 原生操作 Mysql
113 0
|
26天前
|
NoSQL 关系型数据库 MySQL
不是 PHP 不行了,而是 MySQL 数据库扛不住啊
【9月更文挑战第8天】这段内容讨论了MySQL在某些场景下面临的挑战及其原因,并指出这些问题不能完全归咎于MySQL本身。高并发读写压力、数据量增长以及复杂查询和事务处理都可能导致性能瓶颈。然而,应用程序设计不合理、系统架构不佳以及其他数据库选择和优化策略不足也是重要因素。综合考虑这些方面才能有效解决性能问题,而MySQL通过不断改进和优化,仍然是许多应用场景中的可靠选择。
|
2月前
|
存储 SQL 关系型数据库
PHP与MySQL交互的奥秘
【8月更文挑战第29天】在编程的世界里,PHP和MySQL就像是一对默契的舞伴,共同演绎着数据的交响曲。本文将带你探索它们之间的互动,从连接数据库到执行查询,再到处理结果,每一步都充满了节奏与和谐。我们将一起走进这段代码的旅程,感受数据流动的魅力。
|
2月前
|
SQL 关系型数据库 MySQL
PHP与MySQL交互之基础教程
【8月更文挑战第31天】 在数字世界中,数据是推动一切的核心力量。本文将引导你探索PHP与MySQL的协同工作,通过实际代码示例,展示如何建立连接、执行查询以及处理结果集。无论你是初学者还是希望巩固知识的开发者,这篇文章都将为你提供宝贵的实践知识。
|
3月前
|
数据库
基于PHP+MYSQL开发制作的趣味测试网站源码
基于PHP+MYSQL开发制作的趣味测试网站源码。可在后台提前设置好缘分, 自己手动在数据库里修改数据,数据库里有就会优先查询数据库的信息, 没设置的话第一次查询缘分都是非常好的 95-99,第二次查就比较差 , 所以如果要你女朋友查询你的名字觉得很好 那就得是她第一反应是查和你的缘分, 如果查的是别人,那不好意思,第二个可能是你。
53 3
|
5月前
|
关系型数据库 MySQL PHP
|
4月前
|
NoSQL 关系型数据库 MySQL
linux服务器重启php,nginx,redis,mysql命令
linux服务器重启php,nginx,redis,mysql命令
66 1
|
4月前
|
Ubuntu 关系型数据库 MySQL
ubuntu apt 安装wordpress所需所有的 一键脚本 扩展您的PHP似乎没有安装运行WordPress所必需的MySQL扩展。
ubuntu apt 安装wordpress所需所有的 一键脚本 扩展您的PHP似乎没有安装运行WordPress所必需的MySQL扩展。
50 0
ubuntu apt 安装wordpress所需所有的 一键脚本 扩展您的PHP似乎没有安装运行WordPress所必需的MySQL扩展。
|
4月前
|
前端开发 关系型数据库 MySQL
使用 PHP 和 MySQL 实现留言板功能
使用 PHP 和 MySQL 实现留言板功能
118 0