行爲型-職責鏈模式(上)

職責鏈模式又複用和擴展的作用。在實際項目開發中比較常見,特別是像框架開發中,可以利用他們提供框架的擴展點,能夠讓框架使用不修改框架源碼的情況下,基於擴展點制定框架的功能。

官方的定義是將請求的發送和接收接口,讓多個接收對象都有機會處理這個請求,將這些接收對象串成一條鏈,並沿着這條鏈傳遞這個請求,直到鏈上的某個接收對象能夠處理它爲止。

容易理解的話來解讀。在職責鏈模式中,多個處理器,以此處理一個請求,先經過A處理器處理,然後再把請求傳遞給B處理器,B處理器處理完後再傳給C處理器,以此類推,形成一個鏈條,鏈條上的每一個處理器各自承擔各自的處理職責,所以叫做職責鏈模式。

職責鏈模式有多種實現方式,這裏介紹兩種比較常用的,第1種實現方式:Handler是所有處理器類的抽象父類,handle() 是抽象方法。每個具體的處理器類(HandlerA、HandlerB)的handle() 函數的代碼結構類似,如果它能處理該請求,就不繼續往下傳遞;如果不能處理,則交由後面的處理器來處理(也就是調用 successor.handle())。HandlerChain 是處理器鏈,從數據結構的角度來看,它就是一個記錄了鏈頭、鏈尾的鏈表。其中,記錄鏈尾是爲了方便添加處理器。


public abstract class Handler {
  protected Handler successor = null;

  public void setSuccessor(Handler successor) {
    this.successor = successor;
  }

  public abstract void handle();
}

public class HandlerA extends Handler {
  @Override
  public boolean handle() {
    boolean handled = false;
    //...
    if (!handled && successor != null) {
      successor.handle();
    }
  }
}

public class HandlerB extends Handler {
  @Override
  public void handle() {
    boolean handled = false;
    //...
    if (!handled && successor != null) {
      successor.handle();
    } 
  }
}

public class HandlerChain {
  private Handler head = null;
  private Handler tail = null;

  public void addHandler(Handler handler) {
    handler.setSuccessor(null);

    if (head == null) {
      head = handler;
      tail = handler;
      return;
    }

    tail.setSuccessor(handler);
    tail = handler;
  }

  public void handle() {
    if (head != null) {
      head.handle();
    }
  }
}

// 使用舉例
public class Application {
  public static void main(String[] args) {
    HandlerChain chain = new HandlerChain();
    chain.addHandler(new HandlerA());
    chain.addHandler(new HandlerB());
    chain.handle();
  }
}

上面的代碼實現不夠優雅。處理器類的 handle() 函數,不僅包含自己的業務邏輯,還包含對下一個處理器的調用,也就是代碼中的 successor.handle()。一個不熟悉這種代碼結構的程序員,在添加新的處理器類的時候,很有可能忘記在 handle() 函數中調用 successor.handle(),這就會導致代碼出現 bug。針對這個問題,我們對代碼進行重構,利用模板模式,將調用 successor.handle() 的邏輯從具體的處理器類中剝離出來,放到抽象父類中。這樣具體的處理器類只需要實現自己的業務邏輯就可以了。重構之後的代碼如下所示:


public abstract class Handler {
  protected Handler successor = null;

  public void setSuccessor(Handler successor) {
    this.successor = successor;
  }

  public final void handle() {
    boolean handled = doHandle();
    if (successor != null && !handled) {
      successor.handle();
    }
  }

  protected abstract boolean doHandle();
}

public class HandlerA extends Handler {
  @Override
  protected boolean doHandle() {
    boolean handled = false;
    //...
    return handled;
  }
}

public class HandlerB extends Handler {
  @Override
  protected boolean doHandle() {
    boolean handled = false;
    //...
    return handled;
  }
}

// HandlerChain和Application代碼不變

第二種實現方式,更加簡單。HandlerChain類用數組而非鏈表來保存所有的處理器,並且需要在 HandlerChain 的 handle() 函數中,依次調用每個處理器的 handle() 函數。


public interface IHandler {
  boolean handle();
}

public class HandlerA implements IHandler {
  @Override
  public boolean handle() {
    boolean handled = false;
    //...
    return handled;
  }
}

public class HandlerB implements IHandler {
  @Override
  public boolean handle() {
    boolean handled = false;
    //...
    return handled;
  }
}

public class HandlerChain {
  private List<IHandler> handlers = new ArrayList<>();

  public void addHandler(IHandler handler) {
    this.handlers.add(handler);
  }

  public void handle() {
    for (IHandler handler : handlers) {
      boolean handled = handler.handle();
      if (handled) {
        break;
      }
    }
  }
}

// 使用舉例
public class Application {
  public static void main(String[] args) {
    HandlerChain chain = new HandlerChain();
    chain.addHandler(new HandlerA());
    chain.addHandler(new HandlerB());
    chain.handle();
  }
}

在 GoF 給出的定義中,如果處理器鏈上的某個處理器能夠處理這個請求,那就不會繼續往下傳遞請求。實際上,職責鏈模式還有一種變體,那就是請求會被所有的處理器都處理一遍,不存在中途終止的情況。這種變體也有兩種實現方式:用鏈表存儲處理器和用數組存儲處理器,跟上面的兩種實現方式類似,只需要稍微修改即可。

職責鏈模式的應用場景舉例

對於支持 UGC(User Generated Content,用戶生成內容)的應用(比如論壇)來說,用戶生成的內容(比如,在論壇中發表的帖子)可能會包含一些敏感詞(比如涉黃、廣告、反動等詞彙)。針對這個應用場景,我們就可以利用職責鏈模式來過濾這些敏感詞。對於包含敏感詞的內容,有兩種處理方式,一種是直接禁止發佈,另一種是給敏感詞打馬賽克(比如,用 *** 替換敏感詞)之後再發布。第一種處理方式符合 GoF 給出的職責鏈模式的定義,第二種處理方式是職責鏈模式的變體。我們這裏只給出第一種實現方式的代碼示例,如下所示,並且,只給出了代碼實現的骨架,具體的敏感詞過濾算法並沒有給出。


public interface SensitiveWordFilter {
  boolean doFilter(Content content);
}

public class SexyWordFilter implements SensitiveWordFilter {
  @Override
  public boolean doFilter(Content content) {
    boolean legal = true;
    //...
    return legal;
  }
}

// PoliticalWordFilter、AdsWordFilter類代碼結構與SexyWordFilter類似

public class SensitiveWordFilterChain {
  private List<SensitiveWordFilter> filters = new ArrayList<>();

  public void addFilter(SensitiveWordFilter filter) {
    this.filters.add(filter);
  }

  // return true if content doesn't contain sensitive words.
  public boolean filter(Content content) {
    for (SensitiveWordFilter filter : filters) {
      if (!filter.doFilter(content)) {
        return false;
      }
    }
    return true;
  }
}

public class ApplicationDemo {
  public static void main(String[] args) {
    SensitiveWordFilterChain filterChain = new SensitiveWordFilterChain();
    filterChain.addFilter(new AdsWordFilter());
    filterChain.addFilter(new SexyWordFilter());
    filterChain.addFilter(new PoliticalWordFilter());

    boolean legal = filterChain.filter(new Content());
    if (!legal) {
      // 不發表
    } else {
      // 發表
    }
  }
}

爲什麼使用職責鏈模式呢?這是不是過度設計呢?

應用設計模式主要是爲了應對代碼的複雜性,讓其滿足開閉原則,提高代碼的擴展性。這裏應用職責鏈模式也不例外。

首先,我們來看,職責鏈模式如何應對代碼的複雜性。

將大塊代碼邏輯拆分成函數,將大類拆分成小類,是應對代碼複雜性的常用方法。應用職責鏈模式,我們把各個敏感詞過濾函數繼續拆分出來,設計成獨立的類,進一步簡化了 SensitiveWordFilter 類,讓 SensitiveWordFilter 類的代碼不會過多,過複雜。

其次,我們再來看,職責鏈模式如何讓代碼滿足開閉原則,提高代碼的擴展性。

當我們要擴展新的過濾算法的時候,比如,我們還需要過濾特殊符號,按照非職責鏈模式的代碼實現方式,我們需要修改 SensitiveWordFilter 的代碼,違反開閉原則。不過,這樣的修改還算比較集中,也是可以接受的。而職責鏈模式的實現方式更加優雅,只需要新添加一個 Filter 類,並且通過 addFilter() 函數將它添加到 FilterChain 中即可,其他代碼完全不需要修改。

不過,你可能會說,即便使用職責鏈模式來實現,當添加新的過濾算法的時候,還是要修改客戶端代碼(ApplicationDemo),這樣做也沒有完全符合開閉原則。

實際上,細化一下的話,我們可以把上面的代碼分成兩類:框架代碼和客戶端代碼。其中,ApplicationDemo 屬於客戶端代碼,也就是使用框架的代碼。除 ApplicationDemo 之外的代碼屬於敏感詞過濾框架代碼。

假設敏感詞過濾框架並不是我們開發維護的,而是我們引入的一個第三方框架,我們要擴展一個新的過濾算法,不可能直接去修改框架的源碼。這個時候,利用職責鏈模式就能達到開篇所說的,在不修改框架源碼的情況下,基於職責鏈模式提供的擴展點,來擴展新的功能。換句話說,我們在框架這個代碼範圍內實現了開閉原則。

除此之外,利用職責鏈模式相對於不用職責鏈的實現方式,還有一個好處,那就是配置過濾算法更加靈活,可以只選擇使用某幾個過濾算法。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章