zoukankan      html  css  js  c++  java
  • 设计模式之职责链模式

    重温设计模式(三)——职责链模式(chain of responsibility)

     

    一. 写在前面的

    这么多的设计模式,我觉得职责链是我第一次看上去最简单,可是回想起来却又最复杂的一个模式。

    因此,这个文章我酝酿了很久,一直也没有胆量发出来,例子也是改了又改,可是仍然觉得不够合理。所以希望各位多多指教。

    二. 什么是链

    image

    文章伊始,先让我们了解这个最基本的概念,什么是链。

    我给链下了这样的定义:

    1. 链是一系列节点的集合。

    2. 链的各节点可灵活拆分再重组。

    三. 何为职责链

    职责链模式:使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系。将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理他为止。

    图如下:

    chain

    UML很简单,让我们先来看一个简单的例子。

    四. 职责链模式应用之请假管理

    请假这个事情,相信每个人都不陌生。

    我们公司是个相对很宽松的公司。

    在公司里,如果你的请假时间小于0.5天,那么只需要向项目经理打声招呼就OK了。

    如果超过了0.5天,但是还小于2天,那么就要去找人事部处理,当然,这就要扣工资了。

    如果超过了2天,你就需要去找总经理了,工资当然也玩完了。

    那么,对于我们来说,这个流程就是这样的。

    image

    也就是这样一个过程,你需要和你的直接上级——项目经理去打交道,最终可能是项目经理给你回邮件,可能是人事部给你回邮件,也可能是总经理给你回邮件。内部的过程其实应该是个黑盒子,你并不知道内部的消息是如何处理的。你需要找到的,只是你想要第一个交付的对象而已。

    无标题

    那么我们的代码应该是这样的。

    首先我们要写一个请求的类。

    class Request
    {
        private int day;
        private string reason;
        public int Day
        {
            get { return day; }
            set { day = value; }
        }
        public string Reason
        {
            get { return reason; }
            set { reason = value; }
        }
        public Request(int day, string reason)
        {
            this.day = day;
            this.reason = reason;
        }
    }
    接下来看下请求相应者,他们有两个核心方法,一个是相应操作,一个是选择继任者。
    abstract class Boss
    {
        private string name;
        public string Name
        {
            get { return name; }
            set { name = value; }
        }
        private Boss successor;
        public Boss Successor
        {
            get { return successor; }
            set { successor = value; }
        }
        public Boss(string name)
        {
            this.name = name;
        }
        public abstract bool PassRequest(Request request);
    }
    class PM:Boss
    {
        public PM(string name)
            : base(name)
        { }
        public override bool PassRequest(Request request)
        {
            int day = request.Day;
            string reason = request.Reason;
            if (day <= 0.5)
            {
                return true;
            }
            return Successor.PassRequest(request);
        }
    }
    class HR:Boss
    {
        public HR(string name)
            : base(name)
        { }
        public override bool PassRequest(Request request)
        {
            int day = request.Day;
            string reason = request.Reason;
            if (day > 0.5&&day<=2)
            {
                return true;
            }
            return Successor.PassRequest(request);
        }
    }
    class Manager : Boss
    {
        public Manager(string name)
            : base(name)
        { }
        public override bool PassRequest(Request request)
        {
            int day = request.Day;
            string reason = request.Reason;
            if (reason.Equals("正当理由"))
            {
                return true;
            }
            return false;
        }
    }

    那么我们调用的时候就很简单了!

    static void Main(string[] args)
    {
        Request request = new Request(3, "非正当理由");
        Boss pm = new PM("pm");
        Boss hr = new HR("hr");
        Boss manager = new Manager("manager");
        pm.Successor = hr;
        hr.Successor = manager;
        bool pass = pm.PassRequest(request);
        Console.Write(pass);
    }

    五. 灵活在哪?

    让我们来看下职责链究竟灵活在哪?

    1. 改变内部的传递规则。

    无标题

    在内部,项目经理完全可以跳过人事部到那一关直接找到总经理。

    每个人都可以去动态地指定他的继任者。

    2. 可以从职责链任何一关开始。

    如果项目经理不在,那么完全可以写这样的代码:

    static void Main(string[] args)
    {
        Request request = new Request(3, "非正当理由");
        Boss pm = new PM("pm");
        Boss hr = new HR("hr");
        Boss manager = new Manager("manager");
        pm.Successor = hr;
        hr.Successor = manager;
        //bool pass = pm.PassRequest(request);
        bool pass = hr.PassRequest(request);
        Console.Write(pass);
    }

    3. 我们来比较一下,用职责链和不用职责链的区别:

    image

    这是不用职责链我们的结构,我们需要和公司中的每一个层级都发生耦合关系。

    如果反映在代码上即使我们需要在一个类中去写上很多丑陋的if….else语句。

    如果用了职责链,相当于我们面对的是一个黑箱,我们只需要认识其中的一个部门,然后让黑箱内部去负责传递就好了。

    六. 职责链 != 链表

    很多人都愿意把职责链和链表混为一谈,确实,从字面意思上理解,链,链表,很像。可是他们一样么?

    他们区别在哪里:

    让我们看一个链表的典型结构:

    image

    让我们来看一下链表的典型特征:

    1. 链表是一个链状结构,每个节点有一个next属性去指向他的下一节点。

    2. 链表有一个Header节点,然后用户每次必须通过头节点,然后去遍历寻找每一个节点。

    3. 链表遍历操作的复杂度是O(n),但是插入和删除指定节点的复杂度是常数级。

    让我们来着重看这第二点:

    我们来想想在文章开始时我们画出的那个链,一个链,我们可以从头将他拿起,也可以从中间将他拿起:

    image

    也就是说我们用户可以去访问节点中的任何一个节点作为开始节点,这就是链表与职责链不同的地方。

    七. 职责链的扩展——树状链结构

    职责链中,我们之前看到的都是一些单链结构,但是其实在很多情况下,每一个节点都对应着很多其他的部分。

    image 

    那么这样,我们的每一个节点都可以使用一个List来维护他节点的下一节点,甚至可以用组合模式来分别设计每一节点。

    八. 由法律想到——职责链的兜底条款

    仔细想想法律条文,尤其是刑法,经常可以看到这样的条文:

    1. 如果*********,则处以拘役处分。

    2. 如果*********,则处以有期徒刑一年到十年。

    3. 如果*********,则处以有期徒刑十年以上。

    4. 如果*********,则**********。

    5. 如果以上条件皆不满足,则*****************。

    其实最后一条就叫做法律的兜底条款。这给了法官很大的自由裁量权,在一定程度上也降低了犯罪分子钻法律空子的可能性。

    在我们的职责链中,如果不存在这样的兜底条款,那么用户如果不从首节点开始访问,那么就很可能出现异常的情况。于是我们应该为职责链设置一个默认的条款:

    image

    这样的话,任何一个处理无论如何访问,都能得到一个正常的处理。

    九. 职责链的缺点

    让我们继续回到上面的例子,我们发现,其实当请假时间超过2天的时候,PM和HR其实没有做任何的事情,而只是做了一个传递工作。

    而传递工作之后,他们就成了垃圾对象。

    也就是说,他们在实际的处理中,并没有发挥任何的作用。

    那么当这个链结构比较长,比较复杂的话,会产生很多的内存垃圾对象。

    这也就是职责链的最大缺点之所在。

    十. 职责链的乱用

    在和其他的人的讨论中,我发现他们的观点是:

    只要一者传一者,那么就要用职责链。在我们的项目中,他们这样去用:

    abstract class DBHelper
    { 
        
    }
    
    interface IRequestHandler
    {
        IDBHelper ReturnHelper(string dbName);
    }
    class RequestHandler:IRequestHandler
    {
        private RequestHandler successor;
        public RequestHandler Successor
        {
            get { return successor; }
            set { successor = value; }
        }
        public abstract IDBHelper ReturnHelper(string dbName);
    }
    
    class SQLHelper : DBHelper
    { 
    
    }
    class OracleHelper : DBHelper
    { 
    
    }
    class DB2Helper : DBHelper
    { 
    
    }
    class SQL : RequestHandler
    {
        public override IDBHelper ReturnHelper(string dbName)
        {
            if (dbName.Equals("SQL Server"))
            {
                return new SQLHelper();
            }
            return Successor.ReturnHelper(dbName);
        }
    }
    class Oracle : RequestHandler
    {
        public override IDBHelper ReturnHelper(string dbName)
        {
            if (dbName.Equals("Oracle"))
            {
                return new OracleHelper();
            }
            return Successor.ReturnHelper(dbName);
        }
    }
    class DB2 : RequestHandler
    {
        public override IDBHelper ReturnHelper(string dbName)
        {
            if (dbName.Equals("DB2"))
            {
                return new DB2Helper();
            }
            return new SQLHelper();
        }
    }

    这样的话,每个类相当于只负责一个操作。

    那么我们如何改进呢?第一,我们可以用一个工厂来实现。另外,我们可以用表驱动的方式来解决问题。

    十一. 表驱动改进职责链

    表驱动(Table driven),其实就是指用查表的方式来获取值。

    那么我们用标驱动法来改进上面的例子:

    class HelperRequest
    {
        private Dictionary<String, DBHelper> dic = new Dictionary<string, DBHelper>();
        public void Add(string name,DBHelper helper)
        {
            dic.Add(name, helper);
        }
        public DBHelper GetHelper(string name)
        {
            DBHelper helper;
            bool temp = dic.TryGetValue(name, out helper);
            if (temp)
            {
                return helper;
            }
            return null;
        }
    }

    我想一个没有学过设计模式的人都会这样写的。一个学过设计模式很多年的人也会这样写的。

    而怕的就是为了模式而模式,为了职责链而职责链了。

    十二. 职责链在java script中的应用

    我们想象这样一种情况:

    image

    我们都知道,在ASP.NET 的 Webform模型中页面是以控件树的形式去组织的。那么我们用右键点击其中的一个页面,那么这个事件就会找离他最近的控件,如果不存在,那么就去找他的父控件,如此递归下去,直到找到为止。

    这其实就是一种职责链的体现!

    十三. 深析职责链的使用

    职责链模式不能乱用,否则非常容易变成因为模式而模式的反例。

    下面是我归纳出来的一些关于职责链方面的使用规则,只是个人的意见,还希望大家指教。

    1, 如果存在N对N,或者是一般的常规线性关系,那么我们完全可以用表驱动来取代职责链。

    2, 对象本身要经过什么处理是通过每个链上元素通过运行态来决定的,决定的因素是取决于对象的属性或者一些其他方面的策略。

    3, 用户无论是从哪一个节点作为他的请求头节点,最终用户都可以得到一个请求的反馈。

    4, 应怪怪建议,补充同级的处理!职责链并非是严格的上下级的传递,其中也包括同级的传递,职责链一样可以在同级之间做传递。

    例如,继续用我们上面请假的那个做例子,也许我们公司有两个HR,事实上也是这样的,我们把前台“MM”也美称为人力资源部:

    static void Main(string[] args)
    {
        Request request = new Request(3, "非正当理由");
        Boss pm = new PM("pm");
        Boss hr1 = new HR("Real HR");
        Boss hr2 = new HR("QiantaiMM");
        Boss manager = new Manager("manager");
        pm.Successor = hr1;
        hr1.Successor = hr2;
        hr2.Successor = manager;
        bool pass = pm.PassRequest(request);
        Console.Write(pass);
    }

    其实这样也未尝不可。有人也许会说,那么这样的同样一个类的两个对象又有什么意义呢?

    那么我们不妨去试着这样改造这个HR的类。

    enum HRType
    {
        RealHR,
        Qiantai
    }
    class HR:Boss
    {
        private HRType type;
        public HR(string name,HRType type)
            : base(name)
        {
            this.type = type;
        }
        public override bool PassRequest(Request request)
        {
            int day = request.Day;
            if (day>=0.5&&day<2)
            {
                switch (type)
                { 
                    case HRType.RealHR:
                        //扣工资
                        return true;
                        break;
                    case HRType.Qiantai:
                        //不扣工资
                        return true;
                        break;
                }
            }
            return Successor.PassRequest(request);
        }
    }

    这样,因为前台MM容易说话,很可能他就不去扣你的工资,如果你去先找的HR,那么你这天的工资就报销了。

    同理,我们一样可以让他们的职责细化,比如说Real Hr负责0.5天到1天的,而Qiantai去负责1天到2天的,也未尝不可。

    总之,职责链并非是单一的上下级的传递,一样可以实现同级的传递。

    十四. 职责链总结

    职责链是使多个对象都有机会处理请求,从而避免请求的发送者和接受者之间的耦合关系。将这个对象连成一条链,并沿着这条链传递该请求,直到有一个对象处理他为止。

     

     

  • 相关阅读:
    c:forTokens标签循环输出
    jsp转long类型为date,并且格式化
    spring中@Param和mybatis中@Param使用区别(暂时还没接触)
    734. Sentence Similarity 有字典数组的相似句子
    246. Strobogrammatic Number 上下对称的数字
    720. Longest Word in Dictionary 能连续拼接出来的最长单词
    599. Minimum Index Sum of Two Lists两个餐厅列表的索引和最小
    594. Longest Harmonious Subsequence强制差距为1的最长连续
    645. Set Mismatch挑出不匹配的元素和应该真正存在的元素
    409. Longest Palindrome 最长对称串
  • 原文地址:https://www.cnblogs.com/kexb/p/3673261.html
Copyright © 2011-2022 走看看