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


原文发布时间为:2017-10-24

本文作者:佚名

本文来自云栖社区合作伙伴“51CTO”,了解相关信息可以关注。

相关文章
|
4天前
|
PHP 开发者
PHP中的异常处理:提升代码的健壮性
【8月更文挑战第27天】在PHP编程的世界里,异常处理是一块基石,它确保我们的应用程序在遇到不可预见的错误时不会崩溃,而是能够优雅地处理这些情况。本文将深入探讨PHP的异常处理机制,通过实际示例展示如何捕获和处理异常,以及如何利用自定义异常来增强代码的可读性和可维护性。我们将从基础的try-catch结构出发,逐步过渡到更高级的话题,如异常链和错误日志记录,旨在为读者提供一套完整的异常处理策略。
|
2天前
|
PHP
PHP中的面向对象编程:构建更高效的代码
【8月更文挑战第29天】在PHP的世界中,面向对象编程(OOP)是提升代码可维护性、可扩展性和复用性的利器。本文将通过浅显易懂的方式,带领读者探索PHP中OOP的核心概念,并通过实际代码示例揭示如何利用这些概念来编写更加高效和优雅的代码。无论你是PHP新手还是希望深化理解的老手,这篇文章都将为你打开一扇新窗,让你看到编码的艺术与哲学。
|
4天前
|
数据处理 PHP 开发者
PHP中的数组操作技巧:提高代码效率与可读性
【8月更文挑战第27天】在PHP开发中,数组是数据处理的核心。掌握数组的操作不仅能提升代码的执行效率,还能增强代码的可读性。本文将通过实际的编码示例,展示如何在PHP中高效地使用数组,包括数组的创建、遍历、排序、查找和过滤等常用操作,以及一些高级技巧,如使用匿名函数和数组的解构赋值。无论你是新手还是有经验的开发者,这些技巧都将对你的PHP编程之旅大有裨益。
|
7天前
|
设计模式 算法 PHP
深入理解PHP中的数组操作探索编程之美:从代码到架构的思维转变
【8月更文挑战第24天】在PHP编程中,数组是基础且强大的数据结构。本文将通过浅显易懂的方式,介绍如何在PHP中高效地操作数组,包括创建、遍历、排序和过滤等常见任务。无论你是初学者还是有经验的开发者,这篇文章都会带给你新的启示。 【8月更文挑战第24天】在编程的世界中,代码不仅仅是冰冷的字符排列,它承载着思想、解决问题的智慧和创新的灵魂。本文将通过个人的技术感悟,带领读者从编写单一功能的代码片段出发,逐步深入到整个软件架构的设计哲学,探索如何将代码块转化为高效、可维护和可扩展的系统。我们将一起见证,当代码与架构思维相结合时,如何引发技术实践的革命性飞跃。
|
3天前
|
PHP 开发者
探索PHP中的异常处理:提升代码的健壮性
【8月更文挑战第28天】在编程的世界里,错误和异常是避免不了的现实。它们像是路上的坑洼,让代码之旅颠簸不已。PHP作为流行的后端开发语言之一,提供了异常处理机制,帮助开发者优雅地应对这些“坑洼”。本文将带你深入了解PHP中的异常处理,从基础概念到实际应用,让你的代码在遇到问题时能够稳稳当当,而不是一头栽倒。我们将一起学习如何捕获异常、如何处理异常,并且通过一些实用的代码示例,让你的PHP技能更上一层楼。
|
3天前
|
存储 中间件 PHP
Python编程入门:从零到一的代码实践深入理解 PHP 中的中间件模式
【8月更文挑战第28天】本文旨在通过浅显易懂的方式,向初学者介绍Python编程的基础知识,并结合具体代码示例,带领读者一步步实现从零基础到能够独立编写简单程序的转变。文章将围绕Python语言的核心概念进行讲解,并通过实例展示如何应用这些概念解决实际问题。无论你是编程新手还是希望扩展技能的专业人士,这篇文章都将为你打开编程世界的大门。 【8月更文挑战第28天】在PHP的世界中,设计模式是构建可维护和可扩展软件的重要工具。本文将通过浅显易懂的语言和生动的比喻,带领读者深入理解中间件模式如何在PHP应用中发挥魔力,实现请求处理的高效管理。我们将一步步揭开中间件的神秘面纱,从它的定义、工作原理到
|
5天前
|
PHP 开发者 UED
PHP中的异常处理:提升代码的健壮性
【8月更文挑战第27天】在PHP编程中,异常处理是维护程序稳定性和可靠性的关键。本文通过介绍异常处理的基本概念、语法结构以及实际应用场景,旨在帮助开发者构建更加健壮的PHP应用。我们将探讨如何使用try-catch语句来捕获和处理异常,确保程序即使在遇到错误时也能优雅地继续运行或安全地终止。
|
12天前
|
敏捷开发 数据可视化 测试技术
阿里云云效产品使用合集之怎么在php composer安装自己服务器的代码
云效作为一款全面覆盖研发全生命周期管理的云端效能平台,致力于帮助企业实现高效协同、敏捷研发和持续交付。本合集收集整理了用户在使用云效过程中遇到的常见问题,问题涉及项目创建与管理、需求规划与迭代、代码托管与版本控制、自动化测试、持续集成与发布等方面。
|
17天前
|
缓存 PHP 数据库
PHP性能优化实战:从代码到服务器的全方位攻略
在数字化时代,Web应用的性能直接影响用户体验和业务成果。作为流行的后端开发语言,PHP的性能优化是提升网站响应速度的关键。本文将通过实际案例分析,探讨PHP代码级别的优化策略、数据库交互效率提升以及服务器配置调优,旨在帮助开发者全面提升PHP应用性能。
24 0
|
20天前
|
设计模式 算法 数据库连接
PHP编程中的设计模式:提升代码的可维护性与扩展性
在软件开发的世界里,设计模式是解决常见问题的经典方法。对于PHP开发者而言,掌握这些模式不仅能够提高代码质量,还能让开发过程更加高效。本文将介绍几个关键的设计模式,并通过实例展示如何将这些理念应用到实际项目中去。
26 0
下一篇
云函数