PHP面向对象五大原则之单一职责原则(SRP)详解

这篇文章主要介绍了PHP面向对象五大原则之单一职责原则(SRP),结合实例形式详细分析了单一职责原则(SRP)的概念、原理、定于与使用方法,需要的朋友可以参考下。

本文实例讲述了PHP面向对象五大原则之单一职责原则(SRP)。分享给大家供大家参考,具体如下:

单一职责原则(Single Pesponsibility Principle, SRP)

单一职责有两个含义: 一个是避免相同的职责分散到不同的类中, 别一个是避免一个类承担太多职责

为什么要遵守SRP呢?

(1)可以减少类之间的耦合

如果减少类之间的耦合,当需求变化时,只修改一个类,从而也就隔离了变化;如果一个类有多个不同职责,它们耦合在一起,当一个职责发生变化时,可能会影响到其他职责。

(2)提高类的复用性

修改电脑比修理电视机简单多了。主要原因就在于电视机各个部件之间的耦合性太高,而电脑则不同,电脑的内存、硬盘、声卡、网卡、键盘灯等部件都可以很容易地单独拆卸和组装。某个部件坏了,换上新的即可。上面的例子就体现了单一职责的优势。由于使用了单一职责,使得‘组件'可以方便地‘拆卸'和‘组装'。

不遵守SRP会影响对类的复用性。当只需要用该类的某一个职责时,由于它和其他的职责耦合在一起,也就很难分离出。

遵守SRP在实际代码开发中有没有什么应用?有的。以数据持久层为例,所谓的数据持久层主要指的是数据库操作,当然,还包括缓存管理等。这时就需要数据持久层支持多种数据库。应该怎么做?定义多个数据库操作类?想法已经很接近了,再进一步,就是使用工厂模式。

工厂模式(Faction)允许你在代码执行时实例化对象。它之所以被称为工厂模式是因为它负责‘生产对象'。以数据库为例,工厂需要的就是根据不同的参数,生成不同的实例化对象。最简单的工厂就是根据传入的类型名实例化对象,如传入MySQL,就调用MySQL类并实例化,如果是SQLite,则调用 SQLite的类并实例化,甚至还可以处理TXT、Execl等‘类数据库'。

工厂类也就是这样的一个类,它只负责生产对象,而不负责对象的具体内容。

以下是示例

定义一个适配器的接口

  1. interface Db_Adpater
  2. {
  3. /**
  4. * 数据库连接
  5. * @param $config 数据库配置
  6. * @return mixed resource
  7. */
  8. public function connect($config);
  9. /**
  10. * 执行数据库查询
  11. * @param $query 数据库查询的SQL字符串
  12. * @param $handle 连接对象
  13. * @return mixed
  14. */
  15. public function query($query,$handle);
  16. }

定义一个实现了DB_Adpater接口的MySQL数据库操作类

  1. class Db_Adapter_Mysql implements Db_Adpater
  2. {
  3. private $_dbLink; //数据库连接字符串标识
  4. /**
  5. * 数据库连接函数
  6. * @param $config 数据库配置
  7. * @return resource
  8. * @throws Db_Exception
  9. */
  10. public function connect($config)
  11. {
  12. if($this->_dbLink = @mysql_connect($config->host . (emptyempty($config->port) ? '' : ':' . $config->prot) ,$config->user, $config->password, true))
  13. {
  14. if(@mysql_select_db($config->database, $this->_dbLink))
  15. {
  16. if($config->charset)
  17. {
  18. mysql_query("SET NAME '{$config->charset}'", $this->_dbLink);
  19. }
  20. return $this->_dbLink;
  21. }
  22. }
  23. throw new Db_Exception(@mysql_error($this->_dbLink));
  24. }
  25. /**
  26. * 执行数据库查询
  27. * @param $query 数据库查询SQL字符串
  28. * @param $handle 连接对象
  29. * @return resource
  30. */
  31. public function query($query,$handle)
  32. {
  33. if($resource = @mysql_query($query,$handle))
  34. return $resource;
  35. }
  36. }

定义一个实现了DB_Adpater接口的SQLite数据库操作类

  1. class Db_Adapter_sqlite implements Db_Adpater
  2. {
  3. private $_dbLink; //数据库连接字符串标识
  4. public function connect($config)
  5. {
  6. if($this->_dbLink = sqlite_open($config->file, 0666, $error))
  7. {
  8. return $this->_dbLink;
  9. }
  10. throw new Db_Exception($error);
  11. }
  12. public function query($query, $handle)
  13. {
  14. if($resource = @sqlite_query($query,$handle))
  15. {
  16. return $resource;
  17. }
  18. }
  19. }

现在如果需要一个数据库操作的方法怎么做,只需定义一个工厂类,根据传入不同的生成需要的类即可。

  1. class sqlFactory
  2. {
  3. public static function factory($type)
  4. {
  5. if(include_once 'Drivers/' . $type . '.php')
  6. {
  7. $classname = 'Db_Adapter_'.$type;
  8. return new $classname;
  9. }
  10. else
  11. throw new Exception('Driver not found');
  12. }
  13. }

调用时,就可以这么写

$db = sqlFactory::factory('MySQL');

$db = sqlFactory::factory('SQLite');

我们把创建数据库连接这块程序单独拿出来,程序中的CURD就不用关心什么数据库了,只要按照规范使用对应的方法即可。

工厂方法让具体的对象解脱出来,使其不再依赖具体的类,而是抽象。

设计模式里面的命令模式也是SRP的体现,命令模式分离“命令的请求者”和“命令的实现者”方面的职责。举一个很好理解的例子,就是你去餐馆订餐吃饭,餐馆存在顾客、服务员、厨师三个角色。作为顾客,你要列出菜单,传给服务员,由服务员通知厨师去实现。作为服务员,只需要调用准备饭菜这个方法(对厨师喊“该炒菜了”),厨师听到要炒菜的请求,就立即去做饭。在这里,命令的请求和实现就完成了解耦。

模拟这个过程,首先定义厨师角色,厨师进行实际做饭、烧汤的工作。

以下是示例

  1. /**
  2. * 厨师类,命令接受者与执行者
  3. * Class cook
  4. */
  5. class cook
  6. {
  7. public function meal()
  8. {
  9. echo '番茄炒鸡蛋',PHP_EOL;
  10. }
  11. public function drink()
  12. {
  13. echo '紫菜蛋花汤',PHP_EOL;
  14. }
  15. public function ok()
  16. {
  17. echo '完毕',PHP_EOL;
  18. }
  19. }
  20. //然后是命令接口
  21. interface Command
  22. {
  23. public function execute();
  24. }

轮到服务员出场,服务员是命令的传送者,通常你到饭馆吃饭都是叫服务员吧,不能直接叫厨师,一般都是叫“服务员,给我来盘番茄炒西红柿”。所以,服务员是顾客和厨师之间的命令沟通都。

  1. class MealCommand implements Command
  2. {
  3. private $cook;
  4. //绑定命令接受者
  5. public function __construct(cook $cook)
  6. {
  7. $this->cook = $cook;
  8. }
  9. public function execute()
  10. {
  11. $this->cook->meal();//把消息传给厨师,让厨师做饭,下同
  12. }
  13. }
  14. class DrinkCommand implements Command
  15. {
  16. private $cook;
  17. //绑定命令接受者
  18. public function __construct(cook $cook)
  19. {
  20. $this->cook = $cook;
  21. }
  22. public function execute()
  23. {
  24. $this->cook->drink();
  25. }
  26. }

现在顾客可以按照菜单叫服务员了

  1. class cookControl
  2. {
  3. private $mealcommand;
  4. private $drinkcommand;
  5. //将命令发送者绑定以命令接收器上面来
  6. public function addCommand(Command $mealcommand, Command $drinkcommand)
  7. {
  8. $this->mealcommand = $mealcommand;
  9. $this->drinkcommand = $drinkcommand;
  10. }
  11. public function callmeal()
  12. {
  13. $this->mealcommand->execute();
  14. }
  15. public function calldrink()
  16. {
  17. $this->drinkcommand->execute();
  18. }
  19. }

好了,现在完成整个过程

  1. $control = new cookControl;
  2. $cook = new cook;
  3. $mealcommand = new MealCommand($cook);
  4. $drinkcommand = new DrinkCommand($cook);
  5. $control->addCommand($mealcommand,$drinkcommand);
  6. $control->callmeal();
  7. $control->calldrink();

从上面的例子可以看出,原来设计模式并非纯理论的东西,而是来源于实际生活,就连普通的餐馆老板都懂设计模式这门看似高深的学问。其实,在经济和管理活动中对流程的优化就是对各种设计模式的摸索和实践。所以,设计模式并非计算机编程中的专利。事实上,设计模式的起源并不是计算机,而是源于建筑学。

在设计模式方面,不仅以上这两种体现了SRP,还有别的(比如代理模式)也体现了SRP。SRP不只是对类设计有意义,对以模块、子系统为单位的系统架构设计同样有意义。

模块、子系统也应该仅有一个引起它变化的原因,如MVC所倡导的各个层之间的相互分离就是SRP在系统总体设计中的应用。

SRP是最简单的原则之一,也是最难做好的原则之一。我们会很自然地将职责连接在一起。找到并且分离这些职责是软件设计需要达到的目的

一些简单的应用遵循的做法如下:

根据业务流程,把业务对象提炼出来。如果业务的流程的链路太复杂,就把这个业务对象分离为多个单一业务对象。当业务链标准化后,对业务对象的内部情况做进一步处理,把第一次标准化视为最高层抽象,第二次视为次高层抽象,以此类推,直到“恰如其分”的设计层次

职责的分类需要注意。有业务职责,还要有脱离业务的抽象职责,从认识业务到抽象算法是一个层层递进的过程。就好比命令模式中的顾客,服务员和厨师的职责,作为老板(即设计师)的你需要规划好各自的职责范围,即要防止越俎代庖,也要防止互相推诿。