Design – Behavioral Patterns-1

The most basic Chain of Responsibility starts by defining an interface that handles a request (IHandler). Then we add classes that handle one or more scenarios (Handler1 and Handler2):

 Figure 12.2: Class diagram representing the Chain of Responsibility patternFigure 12.2: Class diagram representing the Chain of Responsibility pattern 

A difference between the Chain of Responsibility pattern and many other patterns is that no central dispatcher knows the handlers; all handlers are independent. The consumer receives a handler and tells it to handle the request. Each handler determines if it can process the request. If it can, it processes it. In both cases, it also evaluates if it should forward the request to the next handler in the chain. The handler can execute these two tasks in any order, like executing some logic, sending the request down the chain, then executing more logic when the request comes back (like a pipeline).This pattern allows us to divide complex logic into multiple pieces that handle a single responsibility, improving testability, reusability, and extensibility in the process. Since no orchestrator exists, each chain element is independent, leading to a cohesive and loosely coupled design.

When creating the chain of responsibility, you can order the handlers so that the most requested handlers are closer to the beginning of the chain and the least requested handlers are closer to the end. This helps limit the number of “chain links” that are visited for each request before reaching the right handler.

Enough theory; let’s look at some code.

Project – Message interpreter

Context: We need to create the receiving end of a messaging application where each message is unique, making it impossible to create a single algorithm to handle them all.After analyzing the problem, we decided to build a chain of responsibility where each handler can manage a single message. The pattern seems more than perfect!

This project is based on something that I built years ago. IoT devices were sending bytes (messages) due to limited bandwidth. Then, we had to associate those bytes with real values in a web application. Each message had a fixed header size but a variable body size. The headers were handled in a base handler (template method), and each handler in the chain managed a different message type. For the current example, we keep it simpler than parsing bytes, but the concept is the same.

For our demo application, the messages are as simple as this:

namespace ChainOfResponsibility;
public record class Message(string Name, string?
Payload);

The Name property is used as a discriminator to differentiate messages, and each handler is responsible for doing something with the Payload property.

We won’t do anything with the payload as it is irrelevant to the pattern, but conceptually, that is the logic that should happen.

The handlers are very simple, here’s the interface:

namespace ChainOfResponsibility;
public interface IMessageHandler
{
    void Handle(Message message);
}

The only thing a handler can do is handle a message. Our initial application can handle the following messages:

  • The AlarmTriggeredHandler class handles AlarmTriggered messages.
  • The AlarmPausedHandler class handles AlarmPaused messages.
  • The AlarmStoppedHandler class handles AlarmStopped messages.

The real-world logic is that a machine can send an alarm to a REST API indicating it has reached a certain threshold. Then the REST API can push that information to a UI, send an email, a text message, or whatnot.

An alerted human can then pause the alarm while investigating the issue so other people know the alarm is getting handled.

Finally, a human can go to the physical device and stop the alarm because the person has resolved the issue.

We could extrapolate on many more sub-scenarios, but this is the gist.

The three handlers are very similar and share quite a bit of logic, but we fix that later. In the meantime, we have the following handlers:

namespace ChainOfResponsibility;
public class AlarmTriggeredHandler : IMessageHandler
{
    private readonly IMessageHandler?
_next;
    public AlarmTriggeredHandler(IMessageHandler?
next = null)
    {
        _next = next;
    }
    public void Handle(Message message)
    {
        if (message.Name == “AlarmTriggered”)
        {
            // Do something clever with the Payload
        }
        else
        {
            _next?.Handle(message);
        }
    }
}
public class AlarmPausedHandler : IMessageHandler
{
    private readonly IMessageHandler?
_next;
    public AlarmPausedHandler(IMessageHandler?
next = null)
    {
        _next = next;
    }
    public void Handle(Message message)
    {
        if (message.Name == “AlarmPaused”)
        {
            // Do something clever with the Payload
        }
        else
        {
            _next?.Handle(message);
        }
    }
}
public class AlarmStoppedHandler : IMessageHandler
{
    private readonly IMessageHandler?
_next;
    public AlarmStoppedHandler(IMessageHandler?
next = null)
    {
        _next = next;
    }
    public void Handle(Message message)
    {
        if (message.Name == “AlarmStopped”)
        {
            // Do something clever with the Payload
        }
        else
        {
            _next?.Handle(message);
        }
    }
}

Leave a Reply

Your email address will not be published. Required fields are marked *



          Copyright © 2015-2024 | About | Terms of Service | Privacy Policy