職責鏈模式又複用和擴展的作用。在實際項目開發中比較常見,特別是像框架開發中,可以利用他們提供框架的擴展點,能夠讓框架使用不修改框架源碼的情況下,基於擴展點制定框架的功能。
官方的定義是將請求的發送和接收接口,讓多個接收對象都有機會處理這個請求,將這些接收對象串成一條鏈,並沿着這條鏈傳遞這個請求,直到鏈上的某個接收對象能夠處理它爲止。
容易理解的話來解讀。在職責鏈模式中,多個處理器,以此處理一個請求,先經過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 之外的代碼屬於敏感詞過濾框架代碼。
假設敏感詞過濾框架並不是我們開發維護的,而是我們引入的一個第三方框架,我們要擴展一個新的過濾算法,不可能直接去修改框架的源碼。這個時候,利用職責鏈模式就能達到開篇所說的,在不修改框架源碼的情況下,基於職責鏈模式提供的擴展點,來擴展新的功能。換句話說,我們在框架這個代碼範圍內實現了開閉原則。
除此之外,利用職責鏈模式相對於不用職責鏈的實現方式,還有一個好處,那就是配置過濾算法更加靈活,可以只選擇使用某幾個過濾算法。