函數(shù)式編程下的責任鏈模式

責任鏈模式是一種設(shè)計模式悴灵。在責任鏈模式里,很多對象由每一個對象對其下家的引用而連接起來形成一條鏈。請求在這個鏈上傳遞淹禾,直到鏈上的某一個對象決定處理此請求。發(fā)出這個請求的客戶端并不知道鏈上的哪一個對象最終處理這個請求茴扁,這使得系統(tǒng)可以在不影響客戶端的情況下動態(tài)地重新組織和分配責任铃岔。
比如,一個請假申請就可以很好的描述這種模式:

基于請假審批的責任鏈描述
基于請假審批的責任鏈描述

在“iluwatar/java-design-patterns”上有一個很好的例子:
責任鏈模式
在這個例子中峭火,我們來看看是怎么傳遞責任的毁习。
首先是責任鏈條的某一個鏈:

public class OrcOfficer extends RequestHandler {
    public OrcOfficer(RequestHandler handler) {
       super(handler);
    }
   @Override
   public void handleRequest(Request req) {
       if (req.getRequestType().equals(RequestType.TORTURE_PRISONER)) {
           printHandling(req);
           req.markHandled();
       } else {
           super.handleRequest(req);
       }
   }
   @Override
   public String toString() {
       return "Orc officer";
   }
}

在這個類里,如果要初始化這個類卖丸,必須在構(gòu)造器里給定一個下家public OrcOfficer(RequestHandler handler)纺且,這就形成了一個鏈條了。
其次稍浆,在處理請求的時候载碌,首先是做判斷,如果該它處理的衅枫,就處理了嫁艇,責任鏈的傳遞也就結(jié)束了;如果不該它處理弦撩,則交由父類的方法處理步咪。
那么,我們來看看益楼,父類的handleRequest方法里干了什么呢歧斟?

public void handleRequest(Request req) {
    if (next != null) {
      next.handleRequest(req);
    }
  }

可以看到,該方法首先是問還有沒有下家偏形,如果有静袖,則交給下家處理;如果沒有俊扭,則結(jié)束队橙。
完整的例子,請參考責任鏈模式萨惑。
責任鏈模式解決問題的思想是十分優(yōu)雅的捐康,有很好的擴展性,把一個復雜的問題做了簡單的分解庸蔼,使得我們實現(xiàn)起來相當?shù)暮唵巍?br> 不好的地方解总,是面向?qū)ο笳Z言的弱點,必須實現(xiàn)很多的子類來做責任鏈姐仅,造成了類的眾多和代碼的重復花枫。比如上述例子中刻盐,就有OrcCommanderOrcOfficerOrcSoldier三個子類劳翰,這些子類都存在不同程度的代碼重復敦锌。
我們現(xiàn)在來用函數(shù)式編程來解決面向?qū)ο蟮娜觞c。
首先佳簸,RequestTypeRequest可以保留:

public enum RequestType {
    DEFEND_CASTLE, TORTURE_PRISONER, COLLECT_TAX
}

public class Request {
    private final RequestType requestType;
    private final String requestDescription;
    private boolean handled;
    public Request(final RequestType requestType, final String requestDescription) {
        this.requestType = Objects.requireNonNull(requestType);
        this.requestDescription = Objects.requireNonNull(requestDescription);
    }
    public String getRequestDescription() {
        return requestDescription;
    }
    public RequestType getRequestType() {
        return requestType;
    }
    public void markHandled() {
        this.handled = true;
    }
    public boolean isHandled() {
        return this.handled;
    }
    @Override
    public String toString() {
        return getRequestDescription();
    }
}

RequestHandler類需要做相當?shù)母脑欤?/p>

public class RequestHandler {
    private static final Logger LOGGER = LoggerFactory.getLogger(RequestHandler.class);
    private RequestHandler next = null;
    public void setNext(RequestHandler next) {
        this.next = next;
    }

上面是下家的代碼和設(shè)置下家的代碼乙墙。

private Predicate<Request> predicate;
public void withPredication(Predicate<Request> predicate)
{
    this.predicate = predicate;
}

判斷條件,判斷是否該本節(jié)點處理生均。

private Consumer<Request> action;
public void withAction(Consumer<Request> action)
{
    this.action = action;
}

處理方法听想。
最終對請求的處理:

public void handleRequest(Request req) throws Exception{
    if (this.predicate == null || this.action == null) throw new Exception("必須先通過withPredication和withAction給定判定條件和執(zhí)行方法!");
    if (this.predicate.test(req)) this.action.accept(req);

如果該本節(jié)點處理马胧,則處理哗魂。否則:

    else if (next != null) {
        next.handleRequest(req);
    }
}

扔給下家處理。
下面漓雅,我們來看看OrcKing類如何構(gòu)建責任鏈:

public class OrcKingKing {
    private static final Logger LOGGER = LoggerFactory.getLogger(OrcKingKing.class);
    RequestHandler chain;
    public OrcKingKing() {
        buildChain();
    }
    private void buildChain() {
        RequestHandler orcCommander = new RequestHandler();
        orcCommander.withPredication(comparedWith.apply(DEFEND_CASTLE));
        orcCommander.withAction(actionWith.apply("OrcCommander"));
        RequestHandler orcOfficer = new RequestHandler();
        orcOfficer.withPredication(comparedWith.apply(TORTURE_PRISONER));
        orcOfficer.withAction(actionWith.apply("OrcOffice"));
        orcCommander.setNext(orcOfficer);
        RequestHandler orcSoldier = new RequestHandler();
        orcSoldier.withPredication(comparedWith.apply(COLLECT_TAX));
        orcSoldier.withAction(actionWith.apply("OrcSoldier"));
        orcOfficer.setNext(orcSoldier);
        
        this.chain = orcCommander;
   }
   public void makeRequest(Request req) throws Exception{
       chain.handleRequest(req);
   }
   private Function<RequestType, Predicate<Request>> comparedWith = type -> req -> req.getRequestType().equals(type);
   private Function<String, Consumer<Request>> actionWith = objectType -> req -> {
      LOGGER.info("{} handling request \"{}\"", objectType, req);
      req.markHandled();
  };

這樣录别,就完成了責任鏈的構(gòu)建。
在函數(shù)式編程里邻吞,函數(shù)是可以作為參數(shù)傳遞的组题。我們通過把方法傳遞給某一個對象,達到了多態(tài)的目的抱冷,就不需要做子類繼承了崔列。

RequestHandler orcCommander = new RequestHandler();

orcCommander是一個對象,通過下面的代碼傳遞了兩個運行時的函數(shù):

orcCommander.withPredication(comparedWith.apply(DEFEND_CASTLE));
orcCommander.withAction(actionWith.apply("OrcCommander"));

通過函數(shù)式編程旺遮,函數(shù)可以不斷的抽象赵讯,就像下面的代碼:

private Function<RequestType, Predicate<Request>> comparedWith = type -> req -> req.getRequestType().equals(type);
private Function<String, Consumer<Request>> actionWith = objectType -> req -> {
      LOGGER.info("{} handling request \"{}\"", objectType, req);
      req.markHandled();
  };

通過這種方式傳遞了函數(shù),就避免了子類眾多的問題耿眉,因為在面向?qū)ο蟮恼Z言中边翼,函數(shù)必須依附在類身上,不能獨立存在鸣剪。而函數(shù)式編程中组底,函數(shù)是可以獨立存在的,它也可以像對象一樣筐骇,在運行時進行傳遞债鸡。
最后,我們就可以使用這個責任鏈了:

OrcKingKing king = new OrcKingKing();
king.makeRequest(new Request(RequestType.DEFEND_CASTLE, "defend castle"));
king.makeRequest(new Request(RequestType.TORTURE_PRISONER, "torture prisoner"));
king.makeRequest(new Request(RequestType.COLLECT_TAX, "collect tax"));

看看铛纬,函數(shù)式編程的思路厌均,是不是很有意思!


參考文獻:chain

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末告唆,一起剝皮案震驚了整個濱河市棺弊,隨后出現(xiàn)的幾起案子晶密,更是在濱河造成了極大的恐慌,老刑警劉巖镊屎,帶你破解...
    沈念sama閱讀 219,366評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異茄螃,居然都是意外死亡缝驳,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,521評論 3 395
  • 文/潘曉璐 我一進店門归苍,熙熙樓的掌柜王于貴愁眉苦臉地迎上來用狱,“玉大人,你說我怎么就攤上這事拼弃∠囊粒” “怎么了?”我有些...
    開封第一講書人閱讀 165,689評論 0 356
  • 文/不壞的土叔 我叫張陵吻氧,是天一觀的道長溺忧。 經(jīng)常有香客問我,道長盯孙,這世上最難降的妖魔是什么鲁森? 我笑而不...
    開封第一講書人閱讀 58,925評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮振惰,結(jié)果婚禮上歌溉,老公的妹妹穿的比我還像新娘。我一直安慰自己骑晶,他們只是感情好痛垛,可當我...
    茶點故事閱讀 67,942評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著桶蛔,像睡著了一般匙头。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上仔雷,一...
    開封第一講書人閱讀 51,727評論 1 305
  • 那天乾胶,我揣著相機與錄音,去河邊找鬼朽寞。 笑死识窿,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的脑融。 我是一名探鬼主播喻频,決...
    沈念sama閱讀 40,447評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼肘迎!你這毒婦竟也來了甥温?” 一聲冷哼從身側(cè)響起锻煌,我...
    開封第一講書人閱讀 39,349評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎姻蚓,沒想到半個月后宋梧,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,820評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡狰挡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,990評論 3 337
  • 正文 我和宋清朗相戀三年捂龄,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片加叁。...
    茶點故事閱讀 40,127評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡倦沧,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出它匕,到底是詐尸還是另有隱情展融,我是刑警寧澤,帶...
    沈念sama閱讀 35,812評論 5 346
  • 正文 年R本政府宣布豫柬,位于F島的核電站告希,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏烧给。R本人自食惡果不足惜暂雹,卻給世界環(huán)境...
    茶點故事閱讀 41,471評論 3 331
  • 文/蒙蒙 一进苍、第九天 我趴在偏房一處隱蔽的房頂上張望田炭。 院中可真熱鬧,春花似錦茂翔、人聲如沸驰吓。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,017評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽檬贰。三九已至姑廉,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間翁涤,已是汗流浹背桥言。 一陣腳步聲響...
    開封第一講書人閱讀 33,142評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留葵礼,地道東北人号阿。 一個月前我還...
    沈念sama閱讀 48,388評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像鸳粉,于是被迫代替她去往敵國和親扔涧。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,066評論 2 355

推薦閱讀更多精彩內(nèi)容

  • 設(shè)計模式匯總 一、基礎(chǔ)知識 1. 設(shè)計模式概述 定義:設(shè)計模式(Design Pattern)是一套被反復使用枯夜、多...
    MinoyJet閱讀 3,948評論 1 15
  • 工廠模式類似于現(xiàn)實生活中的工廠可以產(chǎn)生大量相似的商品弯汰,去做同樣的事情,實現(xiàn)同樣的效果;這時候需要使用工廠模式湖雹。簡單...
    舟漁行舟閱讀 7,771評論 2 17
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法咏闪,類相關(guān)的語法,內(nèi)部類的語法摔吏,繼承相關(guān)的語法鸽嫂,異常的語法,線程的語...
    子非魚_t_閱讀 31,644評論 18 399
  • 我們因為共同愛好思維導圖而聚在一起舔腾,為了更好地學習交流溪胶,讓我們成長的步伐更高效和精準一些搂擦,讓我們生活更美好一點稳诚,所...
    藍心百合閱讀 1,129評論 1 2
  • 新入職的芬是職場老員,在職場戰(zhàn)線上磕碰近二三十年的時間瀑踢,深諳職場規(guī)則扳还。憑借二三十年的摸爬滾打得來的職場慧眼,對職場...
    四川一芬閱讀 319評論 0 0