65.9K
CodeProject 正在变化。 阅读更多。
Home

理解和实现 C# 中的责任链模式

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.36/5 (9投票s)

2012年11月16日

CPOL

6分钟阅读

viewsIcon

55343

downloadIcon

529

本文讨论责任链模式。

引言

本文讨论责任链模式。我们将尝试了解何时可以使用此模式以及使用此模式的好处。我们还将介绍 C# 中责任链模式的一个基本实现。

背景

在观察者模式中,我们看到了一个类需要了解另一个类的状态变化的情况。在观察者模式中,一个类可以注册到另一个类,以便在其他类状态更改时获得通知。通知将以事件的形式出现,监听器类可以决定需要采取什么操作。

现在,如果我们进一步考虑这种情况,即监听事件的类将根据某些条件做出决策。如果条件不满足,它将把此事件传递给另一个可以处理此事件的对象。

因此,在这种情况下,我们有一系列对象可以根据某些标准处理事件。所有这些对象将按顺序将事件传递给其他对象。该类是否可以采取行动,或者它需要进一步发送事件,这是该类将包含的逻辑。所有可以处理事件的类都链接在一起,即每个类都包含指向其后继者的句柄,它可以将事件传递给它。

责任链模式就是为了应对这种情况而设计的。在这种模式下,一个对象会监听一个事件,当事件发生时,它会处理该事件。如果该对象有能力采取行动,它将采取行动,否则它将把事件传播给链中的另一个对象,该对象可以处理该事件或进一步传播它。此模式在实现工作流类场景方面非常有用。

GoF 将责任链模式定义为“通过让多个对象有机会处理请求,避免将请求的发送者与其接收者耦合。将接收对象链接起来,并通过链传递请求,直到某个对象处理它。”


为了理解这个类图,我们来看看每个类。

  • Handler:这是所有可以处理事件的类都应该实现的接口或抽象类。
  • ConcreteHandler:这些类能够处理事件或将其进一步传播。
  • Client:这是定义链的类,即所有 ConcreteHandlers 的后继者,也是发起请求到 ConcreteHandler 的类。

使用代码

为了更好地理解这个模式,让我们尝试实现一个简单的基础工作流应用程序。

假设我们有一个组织,团队成员请假时,请求会发送给团队负责人。团队负责人可以批准所有不到 10 天的请假请求。如果请假超过 10 天,则请求将转交给项目负责人。项目负责人最多可以批准 20 天的假期。如果请假超过 20 天,则此请求将转交给人力资源部。人力资源部最多可以批准 30 天的假期。如果请假超过 30 天,则请假申请无法由系统批准,需要手动流程批准。

现在,根据上述要求,如果我们尝试实现责任链模式,我们将需要团队负责人、项目负责人和人力资源部的 ConcreteHandlers 。这些 ConcreteHandlers 将被链接在一起,以便请求可以从 TeamLeader 传递到 ProjectLeader 再到 HR

此外,我们需要一个抽象类来包含通用功能,例如跟踪后继对象、发起请求和事件机制。我们将此类命名为 Employee ConcreteHandler 将包含特定于具体处理程序的逻辑。

让我们开始查看 Handler 抽象类,即 Employee 类。

public abstract class Employee
{
    // Every employee will have a supervisor
    protected Employee supervisor;

    // Event mechanism to know whenever a leave has been applied
    public delegate void OnLeaveApplied(Employee e, Leave l);
    public event OnLeaveApplied onLeaveApplied = null;

    // This will invoke events when the leave will be applied
    // i.e. the actual item will be handed over to the hierarchy of
    // concrete handlers.
    public void LeaveApplied(Employee s, Leave leave)
    {
        if (onLeaveApplied != null)
        {
            onLeaveApplied(this, leave);
        }
    }

    // This is the function which concrete handlers will use to take 
    // action, if they are able to take actions.
    public abstract void ApproveLeave(Leave leave);

    // getter to get the supervisor of current employee
    public Employee Supervisor
    {
        get
        {
            return supervisor;
        }
        set
        {
            supervisor = value;
        }
    }     

    // Using this we can apply for leave
    public void ApplyLeave(Leave l)
    {
        LeaveApplied(this, l);
    }
}

Handler 类负责:

  1. 跟踪此对象的后继者
  2. 实现事件机制以通知和传播请求事件。
  3. 发起请求。

现在我们的 Handler 类已经准备好了,让我们一个一个地看看 ConcreteHandlers 。让我们从 TeamLeader 类开始。

public class TeamLeader : Employee
{
    // team leas can only approve upto 7 days of leave
    const int MAX_LEAVES_CAN_APPROVE = 10;

    // in constructor we will attach the event handler that
    // will check if this employee can process or he need to
    // pass on to next employee
    public TeamLeader()
    {
        this.onLeaveApplied += new OnLeaveApplied(TeamLeader_onLeaveApplied);
    }

    // in this function we will check if this employee can 
    // process or he need to pass on to next employee
    void TeamLeader_onLeaveApplied(Employee e, Leave l)
    {
        // check if we can process this request
        if (l.NumberOfDays < MAX_LEAVES_CAN_APPROVE)
        {
            // process it on our level only
            ApproveLeave(l);
        }
        else
        {
            // if we cant process pass on to the supervisor 
            // so that he can process
            if (Supervisor != null)
            {
                Supervisor.LeaveApplied(this, l);
            }
        }
    }

    // If we can process lets show the output
    public override void ApproveLeave(Leave leave)
    {
        Console.WriteLine("LeaveID: {0} Days: {1} Approver: {2}", 
            leave.LeaveID, leave.NumberOfDays, "Team Leader");
    }
}

此类所做的事情是:

  1. 检查此类是否可以采取行动,即请假天数是否少于 10 天。
  2. 如果此类可以采取行动,则向用户显示响应。
  3. 如果此类无法采取行动,则将请求传递给 ProjectLeader 类,即其后继者。

现在让我们看看 ProjectLeader 类。

class ProjectLeader : Employee
{
    const int MAX_LEAVES_CAN_APPROVE = 20;

    // in constructor we will attach the event handler that
    // will check if this employee can process or he need to
    // pass on to next employee
    public ProjectLeader()
    {
        this.onLeaveApplied += new OnLeaveApplied(ProjectLeader_onLeaveApplied);
    }

    // in this function we will check if this employee can 
    // process or he need to pass on to next employee
    void ProjectLeader_onLeaveApplied(Employee e, Leave l)
    {
        // check if we can process this request
        if (l.NumberOfDays < MAX_LEAVES_CAN_APPROVE)
        {
            // process it on our level only
            ApproveLeave(l);
        }
        else
        {
            // if we cant process pass on to the supervisor 
            // so that he can process
            if (Supervisor != null)
            {
                Supervisor.LeaveApplied(this, l);
            }
        }
    }

    // If we can process lets show the output
    public override void ApproveLeave(Leave leave)
    {
        Console.WriteLine("LeaveID: {0} Days: {1} Approver: {2}", 
            leave.LeaveID, leave.NumberOfDays, "Project Leader");
    }
}

此类所做的事情是:

  1. 检查此类是否可以采取行动,即请假天数是否少于 20 天。
  2. 如果此类可以采取行动,则向用户显示响应。
  3. 如果此类无法采取行动,则将请求传递给 HR 类,即其后继者。

现在让我们看看 HR 类。

class HR : Employee
{
    const int MAX_LEAVES_CAN_APPROVE = 30;

    // in constructor we will attach the event handler that
    // will check if this employee can process or 
    // some other action is needed
    public HR()
    {
        this.onLeaveApplied += new OnLeaveApplied(HR_onLeaveApplied);
    }

    // in this function we will check if this employee can 
    // process or some other action is needed
    void HR_onLeaveApplied(Employee e, Leave l)
    {
        // check if we can process this request
        if (l.NumberOfDays < MAX_LEAVES_CAN_APPROVE)
        {
            // process it on our level only
            ApproveLeave(l);
        }
        else
        {
            // if we cant process pass on to the supervisor 
            // so that he can process
            if (Supervisor != null)
            {
                Supervisor.LeaveApplied(this, l);
            }
            else
            {
                // There is no one up in hierarchy so lets 
                // tell the user what he needs to do now
                Console.WriteLine("Leave application suspended, Please contact HR");
            }
        }
    }

    // If we can process lets show the output
    public override void ApproveLeave(Leave leave)
    {
        Console.WriteLine("LeaveID: {0} Days: {1} Approver: {2}", 
            leave.LeaveID, leave.NumberOfDays, "HR");
    }
}

此类所做的事情是:

  1. 检查此类是否可以采取行动,即请假天数是否少于 30 天。
  2. 如果此类可以采取行动,则向用户显示响应。
  3. 如果此类无法采取行动,则告知用户需要进行手动讨论,并且其请求已被暂停。

实际的操作项,即请假,被封装在一个类中,以提高模块化程度,因此在运行应用程序之前,让我们看看这个 Leave 类是什么样的。

// This is the actual Item that will be used by the concretehandlers
// to determine whther they can act upon this request or not
public class Leave
{
    public Leave(Guid guid, int days)
    {
        leaveID = guid;
        numberOfDays = days;
    }

    Guid leaveID;

    public Guid LeaveID
    {
        get { return leaveID; }
        set { leaveID = value; }
    }
    int numberOfDays;

    public int NumberOfDays
    {
        get { return numberOfDays; }
        set { numberOfDays = value; }
    }
}

最后,我们需要 Client 来设置后继链并发起请求。

class Program
{
    static void Main(string[] args)
    {
        // lets create employees 
        TeamLeader tl = new TeamLeader();
        ProjectLeader pl = new ProjectLeader();
        HR hr = new HR();

        // Now lets set the hierarchy of employees
        tl.Supervisor = pl;
        pl.Supervisor = hr;

        // Now lets apply 5 day leave my TL
        tl.ApplyLeave(new Leave(Guid.NewGuid(), 5));

        // Now lets apply 15 day leave my TL
        tl.ApplyLeave(new Leave(Guid.NewGuid(), 15));

        // Now lets apply 25 day leave my TL
        tl.ApplyLeave(new Leave(Guid.NewGuid(), 25));

        // Now lets apply 35 day leave my TL
        tl.ApplyLeave(new Leave(Guid.NewGuid(), 35));

        Console.ReadLine();
    }
}

因此,我们可以看到这个 Main 函数通过为每个类设置后继者来创建链,并向 TeamLeader 类发起请求。每种场景都发起了一个请求。当我们运行此应用程序时。


因此,我们可以看到每个请求都根据申请的天数被相应的类传递和处理。在总结之前,让我们看一下我们应用程序的类图,并将其与原始 GoF 图进行比较。


关注点

在本文中,我们尝试探讨了责任链模式。我们看到了何时可以使用此模式,使用此模式的好处以及如何使用 C# 对此模式进行基本的粗略实现。本文是从初学者的角度编写的。希望本文内容丰富。

历史

  • 2012年11月16日:初版
© . All rights reserved.