更多优质内容
请关注公众号

面向对象和设计模式(二十三) 对象与数据的流水线处理之职责链模式——PHP实现-张柏沛IT博客

正文内容

面向对象和设计模式(二十三) 对象与数据的流水线处理之职责链模式——PHP实现

栏目:PHP 系列:面向对象与设计模式 发布时间:2023-05-29 23:10 浏览量:930
本系列文章目录
展开/收起

一、职责链模式定义


客户端发送一个请求,请求的接收者被串成一条链,这个请求在这条链上传递,直到链上的某个接收对象能够处理它为止(或者是链上的每一个接收者都会对这个请求作出属于它自己的处理)。


一个请求先经过 A 处理器处理,然后再把请求传递给 B 处理器,B 处理器处理完后再传递给 C 处理器,以此类推,形成一个链条。链条上的每个处理器各自承担各自的处理职责,所以叫作职责链模式。



二、职责链的实现


职责链有两种常见的实现方式,一种是链表方式组织而成的职责链,一种是数组方式组织而成的职责链。但无论是那种方式,职责链中都应该至少存在三类实体:


1. 请求对象(Request 或者 Item),又可以叫成被处理的对象,会被职责链上的处理器处理,经过多个处理器处理后的Request对象里的内容会被改变或者会添加新的内容。
2. 处理器对象(Handler),包含具体的处理逻辑,负责对请求作出处理。
3. 职责链(Chain),作为处理器对象的容器,将多个处理器组织成一个链表或者数组,并且负责请求在处理器之间的传递,将一些流程化的工作从处理器对象解耦出来。


其实请求对象并不需要是一个Object,也可以是一个普通的字符串,或者是一个数组以及任意的结构类型。但是如果将这个被处理的内容封装成一个对象之后,那么这个被处理的内容就能够被规范化,处理器就能清楚的知道它有哪些属性,处理器对这个“被处理的内容”的处理方式也能规范化。


而且,职责链模式对请求对象的处理策略也可以是多变的。它可以是:
1. 职责链中只有1个处理对象能成功处理,其余处理对象需要判断自己能否对请求对象进行处理,如果不能则把这个请求对象往下一个处理器传递;如果发现自己能处理,则处理完之后终止请求的传递。
2. 职责链的每个处理对象能对请求对象作出处理,如果处理成功则往下传递,如果处理失败可以停止传递或者依旧往下传递。
3. 由业务层根据自身需求,自由定义职责链的传递策略,例如如果A处理器处理成功,就跳过B和C处理器,直接转交D处理器处理。这种方式更灵活,但是实现起来也更加复杂。

接下来我们以第一种处理策略来分别实现链表方式和数组方式的职责链模式。


方式一:链表方式

<?php

class Item{
    protected $handled = false;     // 是否已被处理
    protected $data;        // 承载需要被处理的数据,可以是任意数据结构的数据
    protected $middleData = [];  // 处理器处理过程中产生的中间数据,可以用来作为多个处理器间的数据交互
    protected $error;       // Throwable异常对象
    
    public function getHandled(){
        return $this->handled;
    }

    public function setHandled($flag){
        $this->handled = $flag;
    }

    public function getData(){
        return $this->data;
    }

    public function setData($data){
        $this->data = $data;
    }

    public function setMiddleData($key, $middleData){
        $this->middleData[$key] = $middleData;
    }

    public function getMiddleData($key){
        return $this->middleData[$key];
    }

    public function getError(){
        return $this->error;
    }

    public function setError($error){
        $this->error = $error;
    }
}

/**
 * @property Item $item
 * @property Handler $successor
 */
abstract class Handler{
    protected $item;
    protected $successor;
    
    public function setItem(Item $item){
        $this->item = $item;
    }

    public function setSuccessor(Handler $successor){
        $this->successor = $successor;
    }

    public function handle(){
        if($this->item->getHandled()){
            return;
        }

        if(!$this->canHandle()){
            $this->successor && $this->successor->handle();         // 链表式的职责链容易造成handle()方法的调用多层嵌套
            return;
        }

        try{
            $newData = $this->doHandle($this->item->getData());
            $this->item->setData($newData);
        }catch(\Throwable $e){
            $this->item->setError($e);
        }
    }

    abstract public function canHandle():bool;          // 判断当前处理类是否能处理该请求对象

    abstract public function doHandle($data);       // doHandle()方法需要返回处理后的$data,如果处理失败需要抛出异常
}

class HandlerA extends Handler{
    public function canHandle():bool{return true;}
    public function doHandle($data){
        // ...
        return $data;
    }
}

class HandlerB extends Handler{ 
    // ...省略
}

class HandlerChain{
    protected $head;    // 头结点
    protected $tail;    // 尾结点
    protected $item;

    public function __construct(Item $item){
        $this->item = $item;
    }

    public function addHandler(Handler $handler){
        $handler->setItem($this->item);

        if($this->head == null){
            $this->head= $this->tail = $handler;
        }else{
            $this->tail->setSuccessor($handler);
            $this->tail = $handler;
        }
    }

    public function handle(){
        $this->head->handle();
        return $this->item;
    }
}


方式二:数组方式

// Item对象没有变

/**
 * @property Item $item
 * @property Handler $successor
 */
abstract class Handler{
    protected $item;
    public function setItem(Item $item){
        $this->item = $item;
    }
    public function handle(){
        try{
            $newData = $this->doHandle($this->item->getData());
            $this->item->setData($newData);
        }catch(\Throwable $e){
            $this->item->setError($e);
        }
    }

    abstract public function canHandle():bool;          // 判断当前处理类是否能处理该请求对象

    abstract public function doHandle($data);       // doHandle()方法需要返回处理后的$data,如果处理失败需要抛出异常
}

class HandlerChain{
    protected $handlers = [];
    protected $item;

    public function __construct(Item $item){
        $this->item = $item;
    }

    public function addHandler(Handler $handler){
        $handler->setItem($this->item);
        $this->handlers[] = $handler;
    }

    public function handle(){
        foreach($this->handlers as $handler){
            if(!$handler->canHandle()){
                continue;
            }
            $handler->handle();     // 一旦有处理器能处理,则处理完之后跳出循环
            break;
        }
        return $this->item;
    }
}



三、职责链模式的应用场景举例


以过滤敏感词为例,对于包含敏感词的内容,我们有两种处理方式,一种是直接禁止发布,另一种是给敏感词打马赛克再发布。这里给出第一种实现方式的代码示例,如下所示:

<?php

  interface SensitiveWordFilter {
    public function doFilter(Content $content);
  }
  
  // 色情词汇过滤器
  class SexyWordFilter implements SensitiveWordFilter {
    public function doFilter(Content $content) {
      $legal = true;
      //...
      return legal;
    }
  }
  
  // PoliticalWordFilter、AdsWordFilter类代码结构与SexyWordFilter类似
  
  class SensitiveWordFilterChain {
    public $filters = [];
  
    public function addFilter(SensitiveWordFilter $filter) {
      $this->filters[] = $filter;
    }
  
    // return true if $content doesn't contain sensitive words.
    public function filter(Content $content) {
      foreach ($this->filters as $filter) {
        if (!$filter->doFilter($content)) {
          return false;
        }
      }
      return true;
    }
  }
  
  class ApplicationDemo {
    public static function main() {
      $filterChain = new SensitiveWordFilterChain();
      $filterChain->addFilter(new AdsWordFilter());
      $filterChain->addFilter(new SexyWordFilter());
      $filterChain->addFilter(new PoliticalWordFilter());
  
      $legal = $filterChain->filter(new Content());
      if (!$legal) {
        // 不发表
      } else {
        // 发表
      }
    }
  }


下面是使用面向过程编写的过滤器代码:

<?php
class SensitiveWordFilter {
  // return true if $content doesn't contain sensitive words.
  public function filter(Content $content) {
    if (!$this->filterSexyWord($content)) {
      return false;
    }

    if (!$this->filterAdsWord($content)) {
      return false;
    }

    if (!$this->filterPoliticalWord($content)) {
      return false;
    }

    return true;
  }

  private function filterSexyWord(Content $content) {
    //....
  }

  private function filterAdsWord(Content $content) {
    //...
  }

  private function filterPoliticalWord(Content $content) {
    //...
  }
}


虽然后者看上去更加简单简洁,但是却不易于扩展新的过滤方法,主要体现在两个方面:

一旦扩展新的过滤方法,就要在filter方法中加入逻辑,违反开闭原则;

如果每种过滤方法都是复杂的逻辑,所有过滤方法都堆在SensitiveWordFilter类中,会使得SensitiveWordFilter类过于复杂,不如将每种过滤方法作为一个单独的类,满足单一职责原则减轻代码复杂度。


最后,使用职责链模式可以让客户端灵活的选择使用哪些过滤方式,不使用哪些过滤方式,这是后者无法做到的。




更多内容请关注微信公众号
zbpblog微信公众号

如果您需要转载,可以点击下方按钮可以进行复制粘贴;本站博客文章为原创,请转载时注明以下信息

张柏沛IT技术博客 > 面向对象和设计模式(二十三) 对象与数据的流水线处理之职责链模式——PHP实现

热门推荐
推荐新闻