24.3职责链模式

职责链模式(Chain of Responsibility):使多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理它为止。
职责链模式(Chain of Responsibility)结构图 第二十四章 加薪非要老总批?——职责链模式 - 图1代码示例
Handler 类,定义一个处理请示的接口。

  1. abstract class Handler
  2. {
  3. protected $successor = null;
  4. //设置继任者
  5. public function SetSuccessor($successor)
  6. {
  7. $this->successor = $successor;
  8. }
  9. //处理请求的抽象方法
  10. abstract function HandleRequest($request);
  11. }

ConcreteHandler 类,具体处理者类,处理它所负责的请求,可访问它的后继者,如果可处理该请求,就处理之,否则就将该请求转发给它的后继者。
ConcreteHandler1,当请求数在 0 到 10 之间则有权处理,否则转到下一位。

  1. class ConcreteHandler1 extends Handler
  2. {
  3. public function HandleRequest($request)
  4. {
  5. //0 到 10,处理此请求
  6. if ($request >= 0 && $request < 10) {
  7. echo $request . ' 处理请求:' . __CLASS__ . PHP_EOL;
  8. } else if ($this->successor != null) {
  9. //转移到下一位
  10. $this->successor->HandleRequest($request);
  11. }
  12. }
  13. }

ConcreteHandler2,当请求数在 10 到 20 之间则有权处理,否则转到下一位。

  1. class ConcreteHandler2 extends Handler
  2. {
  3. public function HandleRequest($request)
  4. {
  5. //10 到 20,处理此请求
  6. if ($request >= 10 && $request < 20) {
  7. echo $request . ' 处理请求:' . __CLASS__ . PHP_EOL;
  8. } else if ($this->successor != null) {
  9. //转移到下一位
  10. $this->successor->HandleRequest($request);
  11. }
  12. }
  13. }

ConcreteHandler3,当请求数在 20 到 30 之间则有权处理,否则转到下一位。

  1. class ConcreteHandler3 extends Handler
  2. {
  3. public function HandleRequest($request)
  4. {
  5. //20 到 30,处理此请求
  6. if ($request >= 20 && $request < 30) {
  7. echo $request . ' 处理请求:' . __CLASS__ . PHP_EOL;
  8. } else if ($this->successor != null) {
  9. //转移到下一位
  10. $this->successor->HandleRequest($request);
  11. }
  12. }
  13. }

客户端代码,向链上的具体处理者对象提交请求。

  1. public function chinaOfResponsibilityDemo()
  2. {
  3. $h1 = new ConcreteHandler1();
  4. $h2 = new ConcreteHandler2();
  5. $h3 = new ConcreteHandler3();
  6. //设置职责链上家与下家
  7. $h1->SetSuccessor($h2);
  8. $h2->SetSuccessor($h3);
  9. $request = array(2, 5, 14, 22, 18, 3, 27, 20);
  10. //循环给最小处理者提交请求,不同的数额,
  11. //由不同的权限处理者处理
  12. foreach ($request as $value) {
  13. $h1->HandleRequest($value);
  14. }
  15. }

24.4职责链的好处

职责链模式的好处:

  • 当客户提交一个请求时,请求是沿链传递直至有一个 ConcreteHandler 对象负责处理它。
  • 接收者和发送者都没有对方的明确信息,且链中的对象自己也并不知道链的结构。结果是职责链可简化对象的相互连接,他们仅需保持一个指向其后继者的引用,而不需保持它所有的候选接收者的引用。这也就大大降低了耦合度了。
  • 由于是在客户端来定义链的结构,也就是说,可以随时地增加或修改处理一个请求的结构。增强了给对象指派职责的灵活性。

这的确是很灵活,不过也要当心,一个请求极有可能到了链的末端都得不到处理,或者因为没有正确配置而得不到处理,这就很糟糕。需要事先考虑全面。
就加薪的例子而言,最重要的有两点:
一个是你需要事先给每个具体管理者设置他的上司是哪个类,也就是设置后继者。
另一点是你需要在每个具体管理者处理请求时,做出判断,是可以处理这个请求,还是必须要‘推卸责任’,转移给后继者去处理。

24.5加薪代码重构

代码结构图 第二十四章 加薪非要老总批?——职责链模式 - 图2代码实现
管理者类

  1. //管理者
  2. abstract class Manager
  3. {
  4. protected $name = '';
  5. //管理者的上级
  6. protected $superior = null;
  7. public function __construct($name)
  8. {
  9. $this->name = $name;
  10. }
  11. //设置管理者的上级
  12. public function SetSuperior($superior)
  13. {
  14. $this->superior = $superior;
  15. }
  16. //申请请求
  17. abstract function RequestApplications($request);
  18. }

经理类就可以去继承这个‘管理者’类,只需重写‘申请请求’的方法就可以了。

  1. //经理
  2. class CommonManager extends Manager
  3. {
  4. public function __construct($name)
  5. {
  6. parent::__construct($name);
  7. }
  8. public function RequestApplications($request)
  9. {
  10. //经理所能有的权限就是可准许下属两天内的假期
  11. if ($request->getRequestType() == '请假' && $request->getNumber() <= 2) {
  12. echo $this->name . ':' . $request->getRequestContent() . ' 数量:' . $request->getNumber() . ' 被批准' . PHP_EOL;
  13. } else {
  14. //其余的申请都需转到上级
  15. if ($this->superior != null) {
  16. $this->superior->RequestApplications($request);
  17. }
  18. }
  19. }
  20. }

‘总监’类同样继承‘管理者类’。

  1. //总监
  2. class Majordomo extends Manager
  3. {
  4. public function __construct($name)
  5. {
  6. parent::__construct($name);
  7. }
  8. public function RequestApplications($request)
  9. {
  10. //总监所能有的权限就是可准许下属一周内的假期
  11. if ($request->getRequestType() == '请假' && $request->getNumber() <= 5) {
  12. echo $this->name . ':' . $request->getRequestContent() . ' 数量:' . $request->getNumber() . ' 被批准' . PHP_EOL;
  13. } else {
  14. //其余的申请都需转到上级
  15. if ($this->superior != null) {
  16. $this->superior->RequestApplications($request);
  17. }
  18. }
  19. }
  20. }

‘总经理’的权限就是全部都需要处理。

  1. //总经理
  2. class GeneralManager extends Manager
  3. {
  4. public function __construct($name)
  5. {
  6. parent::__construct($name);
  7. }
  8. public function RequestApplications($request)
  9. {
  10. //总经理可准许下属任意天的假期
  11. if ($request->getRequestType() == '请假') {
  12. echo $this->name . ':' . $request->getRequestContent() . ' 数量:' . $request->getNumber() . ' 被批准' . PHP_EOL;
  13. } else if ($request->getRequestType() == '加薪' && $request->getNumber() <= 500) { //加薪在500以内,没有问题
  14. echo $this->name . ':' . $request->getRequestContent() . ' 数量:' . $request->getNumber() . ' 被批准' . PHP_EOL;
  15. } else if ($request->getRequestType() == '加薪' && $request->getNumber() > 500) { //超过500,就要考虑一下了
  16. echo $this->name . ':' . $request->getRequestContent() . ' 数量:' . $request->getNumber() . ' 再说吧' . PHP_EOL;
  17. }
  18. }
  19. }

客户端代码

  1. public function chainOfResponsibilityImp()
  2. {
  3. $jingli = new CommonManager('经理');
  4. $zongjian = new Majordomo('总监');
  5. $zongjingli = new GeneralManager('总经理');
  6. //设置上级,完全可以根据实际需求来更改设置
  7. $jingli->SetSuperior($zongjian);
  8. $zongjian->SetSuperior($zongjingli);
  9. //客户端申请都是由‘经理’发起,但实际谁来决策由具体管理类
  10. //来处理,客户端不知道
  11. $request = new Request();
  12. $request->setRequestType('请假');
  13. $request->setRequestContent('小菜请假');
  14. $request->setNumber(1);
  15. $jingli->RequestApplications($request);
  16. $request2 = new Request();
  17. $request2->setRequestType('请假');
  18. $request2->setRequestContent('小菜请假');
  19. $request2->setNumber(4);
  20. $jingli->RequestApplications($request2);
  21. $request3 = new Request();
  22. $request3->setRequestType('加薪');
  23. $request3->setRequestContent('小菜请求加薪');
  24. $request3->setNumber(500);
  25. $jingli->RequestApplications($request3);
  26. $request4 = new Request();
  27. $request4->setRequestType('加薪');
  28. $request4->setRequestContent('小菜请求加薪');
  29. $request4->setNumber(1000);
  30. $jingli->RequestApplications($request4);
  31. }

结果显示

  1. 经理:小菜请假 数量:1 被批准
  2. 总监:小菜请假 数量:4 被批准
  3. 总经理:小菜请求加薪 数量:500 被批准
  4. 总经理:小菜请求加薪 数量:1000 再说吧

这的确是很好地解决了原来大量的分支判断造成难维护、灵活性差的问题。