SoFunction
Updated on 2025-03-07

Detailed explanation of the C# design pattern's responsibility chain model example

Preface

In software development, we usually encounter a scenario where a request will be processed by many modules in the system in turn. If a module cannot be processed, the request will be passed to the next module. For example, in order processing, it must first pass the user verification and the product inventory is sufficient. If the condition is not met, an error will be returned. If the condition is met, it will be processed to the next step.

There is the concept of middleware intermediate key in Core. Every request comes in and passes through a series of Handlers. This is a chain of responsibility mode. Each Handler will decide whether to process the request and whether to pass the request to the request to continue processing.

In .NET delegates, there is also a delegate chain concept. When multiple objects register the same event, the object places the delegate on a chain and processed in turn.

In the event model of JavaScript or WPF, events are bubbled and sinking, and events can be passed to superior or subordinate objects one by one. Each object will decide whether to respond to the event or terminate the event's continued transmission.

These are typical chain of responsibilities patterns, which create a chain of recipient objects for requests, each recipient containing a reference to another recipient. If an object cannot process the request, it passes the same request to the next receiver, passing the request along the chain until an object processes it. The client that makes this request does not know which object on the chain ultimately handles the request, which allows the system to dynamically reorganize and assign responsibilities without affecting the client.

Example 1

Suppose in a computer game, each character has two attributes: attack value and defense value.

public class Creature
{
 public string Name;
 public int Attack, Defense;
 public Creature(string name, int attack, int defense)
 {
 Name = name;
 Attack = attack;
 Defense = defense;
 }

 public override string ToString()
 {
 return $"Name:{Name} Attack:{Attack} Defense:{Defense}";
 }
}

Now this character will be active in the game, and he may pick up some weapons to increase his attack value or defense value. We use CreatureModifer to modify the attack value or defense value of the object. Usually, there will be multiple modifiers in the game that modify the same character, such as picking up weapon A, then picking up weapon B, etc., so we can attach these modifiers to the Creature object in order and modify them one by one.

In the classic chain of responsibilities implementation mode, the CreatureModifier object can be defined in the following way:

public class CreatureModifier
{
 protected Creature creature;
 protected CreatureModifier next;
 public CreatureModifier(Creature creature)
 {
  = creature;
 }
 public void Add(CreatureModifier m)
 {
 if (next != null)
 {
 (m);
 }
 else
 {
 next = m;
 }
 }
 public virtual void Handle()
 {
 next?.Handle();
 }
}

In this class:

  • The constructor saves references to the Creature object to be modified.
  • This class does not do much work, but it is not an abstract class, and other classes can inherit the object.
  • The Add method can add other CreatureModifier classes to the processing chain. If the current modified object is empty, then the value is assigned, otherwise it will be added to the end of the processing chain.
  • The Handle method simply calls the Handle method that handles the object on the chain. Subclasses can override this method to implement specific operations.

Now, you can define a specific modification class that can double the attack value of the character.

public class DoubleAttackModifier : CreatureModifier
{
 public DoubleAttackModifier(Creature c) : base(c)
 {
 }

 public override void Handle()
 {
  *= 2;
 ($"Doubling {}'s attack,Attack:{},Defense:{}");
 ();
 }
}

This class inherits from the CreatureModifier class and rewrites the Handle method. It does two things in the method. One is to double the Attack attribute, and the other is very important, which is to call the Handle method of the base class to allow the modifier on the responsibility chain to continue. Never forget to call it, otherwise the chain will terminate here and will not continue to be passed down.

Next, create a new modifier to increase the defense value. If the attack value is less than 2, the defense value will be increased by 1:

public class IncreaseDefenseModifier : CreatureModifier
{
 public IncreaseDefenseModifier(Creature creature) : base(creature)
 {
 }
 public override void Handle()
 {
 if ( <= 2)
 {
 ($"Increasing {}'s defense");
 ++;
 }
 ();
 }
}

Now the entire application code is as follows:

Creature creature = new Creature("yy", 1, 1);
(creature);
CreatureModifier modi = new CreatureModifier(creature);
(new DoubleAttackModifier(creature));//attack 2,defense 1
(new DoubleAttackModifier(creature));//attack 4,defense 1
(new IncreaseDefenseModifier(creature));//attack 4,defense 1
();

It can be seen that the third IncreaseDefenseModifier does not meet the condition that attack is less than or equal to 2, so the Defense has not been modified.

Example 2

The following example comes from/, first define a method that contains a process chain to establish, and also define a method that handles requests:

public interface IHandle
{
 IHandle SetNext(IHandle handle);
 object Handle(object request);
}

Define an abstract class to set up the responsibility chain and handle requests on the responsibility chain.

public abstract class AbstractHandle : IHandle
{
 private IHandle _nextHandle;

 public IHandle SetNext(IHandle handle)
 {
 this._nextHandle = handle;
 return handle;
 }

 public virtual object Handle(object request)
 {
 if (this._nextHandle != null)
 {
 return this._nextHandle.Handle(request);
 }
 else
 {
 return null;
 }
 }
}

Specific classes to handle on defining several specific responsibility chains:

public class MonkeyHandle : AbstractHandle
{
 public override object Handle(object request)
 {
 if (() == "Banana")
 {
  return $"Monkey: I'll eat the {()}.\n";
 }
 else
 {
  return (request);
 }
 }
}

public class SquirrelHandler : AbstractHandle
{
 public override object Handle(object request)
 {
 if (() == "Nut")
 {
  return $"Squirrel: I'll eat the {()}.\n";
 }
 else
 {
  return (request);
 }
 }
}

public class DogHandler : AbstractHandle
{
 public override object Handle(object request)
 {
 if (() == "MeatBall")
 {
  return $"Dog: I'll eat the {()}.\n";
 }
 else
 {
  return (request);
 }
 }
}

Define the usage method again, the parameters are single responsibility chain parameters:

public static void ClientCode(AbstractHandler handler)
{
 foreach (var food in new List<string> { "Nut", "Banana", "Cup of coffee" })
 {
 ($"Client: Who wants a {food}?");

 var result = (food);

 if (result != null)
 {
  ($" {result}");
 }
 else
 {
  ($" {food} was left untouched.");
 }
 }
}

Now define the process processing chain:

// The other part of the client code constructs the actual chain.
var monkey = new MonkeyHandler();
var squirrel = new SquirrelHandler();
var dog = new DogHandler();

(squirrel).SetNext(dog);

// The client should be able to send a request to any handler, not
// just the first one in the chain.
("Chain: Monkey > Squirrel > Dog\n");
ClientCode(monkey);
();

("Subchain: Squirrel > Dog\n");
ClientCode(squirrel);

The output result is:

Chain: Monkey > Squirrel > Dog

Client: Who wants a Nut?
   Squirrel: I'll eat the Nut.
Client: Who wants a Banana?
   Monkey: I'll eat the Banana.
Client: Who wants a Cup of coffee?
   Cup of coffee was left untouched.

Subchain: Squirrel > Dog

Client: Who wants a Nut?
   Squirrel: I'll eat the Nut.
Client: Who wants a Banana?
   Banana was left untouched.
Client: Who wants a Cup of coffee?
   Cup of coffee was left untouched.

Summarize

The chain of responsibilities is a very simple design pattern. This pattern can be used when requests such as commands or queries need to be processed sequentially. The easiest way to implement it is that each object refers to the next pending object, which can be implemented using a List or LinkList.

This is the article about the responsibility chain model of C# design model. For more information about the responsibility chain model of C# design model, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!

refer to

/design-patterns/chain-of-responsibility

/questions/48851112/is-the-chain-of-responsibility-used-in-the-net-framework

/article/