Sunday 17 March 2019

Chain of Responsibility Design Pattern

Today lets talk about my favourite subject "Design Patterns".

When it comes to design pattern, no one does the job better than "SourceMaking"(https://sourcemaking.com/design_patterns/)

And if you are bored of this creepy creature spelling out some binaries, our dear "Rocket Raccoon" AKA Triangle Faced Monkey, does explain the topic very well as "Refactoring Guru" (https://refactoring.guru/design-patterns/)

Today I will discuss about one of the behavioural design pattern : CHAIN OF RESPONSIBILITY.

We always argue, why do we even need design pattern? few people admit that they don't remember using one. Well few argue that it reduces readability and un-necessarily makes it complicated, when same thing can be achieved with less effort and time in a simpler approach.

Well, I'd say no one should right dirty code just to make it readable. So I decide to bug people with complex code so that we all learn design patterns.

I'm going to take a very simple example and this might not be the best use case.
(Note : Design Patterns are used to solve complex business problems which other wise will require too much dirty code and more maintenance.)

Without wasting any more time lets BEGIN!!!

Have you ever come across this code :

       
if(priority4 is available)

{
  // Execute
}

if(priority3 is available)
{
   // Execute
}

if(priority2 is available)
{
  // Execute
}

if(priority1 is available)
{
  // Execute
}


The worst part is, if you have any additional conditions that may emerge in the future, all those needs to be added here as well. Now you can argue that is readable but are you proud of yourself now ?? :p

So here comes the saviour, the prince of peace : the chain of responsibility

Let's say you have enum with 5 types and at run time, based on the different conditions, you want to set the enum type. Lets see how we can achieve this using this pattern:

We create a class called EnumObjectSelector

    public abstract class EnumObjectSelector

    {
        ///    The parent object type which is an enum. Set based on conditions/rules.
        public static EnumType ObjectType;
        
        ///    The next rule to check / verify.
        private EnumObjectSelector _nextRule;

        ///    Sets the next/successor rule to check in the sequence.
        public void SetSuccessorRule(EnumObjectSelector nextRule)
        {
           _nextRule = nextRule;
        }

        ///    The method used to execute the rule.
        public virtual EnumType ExecuteRule(Model model)
        {
            if(null != nextRule)
            {
                return _nextRule.ExecuteRule(model);
            }
            return ObjectType;
        }
    }

    ///    The Primary rule.
    public class PrimaryRule : EnumObjectSelector

    {
        ///    Constructor.
        ///    Initialise the sequence of the rule to be checked.
        public PrimaryRule()
        {
            Priority4Rule priority4Rule = new Priority4Rule();
            SetSuccessorRule(priority4Rule);

            Priority3Rule priority3Rule = new Priority3Rule();
            priority4Rule.SetSuccessorRule(Priority3Rule);

            Priority2Rule priority2Rule = new Priority2Rule();
            Priority3Rule.SetSuccessorRule(priority2Rule);

            Priority1Rule priority1Rule = new Priority1Rule();
            priority2Rule.SetSuccessorRule(priority1Rule);
        }

        public override EnumType ExecuteRule(Model model)
        {
            if(model.isType5)
            {
                ObjectType = EnumType.ObjectType5;
            }
            return base.ExecuteRule(model);
        }
    }
   
    ///    The priority 4 rule.
    public class Priority4Rule : EnumObjectSelector
    {
        public override EnumType ExecuteRule(Model model)
        {
            if (model.isType4)
            {
                ObjectType = EnumType.ObjectType4;
            }
            return base.ExecuteRule(model);
        }
    }

    ///    The Priority 3 rule.
    public class Priority3Rule : EnumObjectSelector
    {
       public override EnumType ExecuteRule(Model model)

        {
           if (model.isType3)
           {
               ObjectType = EnumType.ObjectType3;
           }
            return base.ExecuteRule(model);
        }
    }

    ///    The Priority 2 rule.
    public class Priority2Rule : EnumObjectSelector
    {
        public override EnumType ExecuteRule(Model model)
        {
            if (model.isType2)
            {
                ObjectType = EnumType.ObjectType2;
            }
            return base.ExecuteRule(model);
        }
    }


    ///    The Priority 1 rule.
    public class Priority1Rule : EnumObjectSelector
    {
        public override EnumType ExecuteRule(Model model)
        {
            if (model.isType1)
            {
                ObjectType = EnumType.ObjectType1;
            }
            return base.ExecuteRule(model);
        }
    }

Now in the main class we just invoke this chain of responsibility as below :


// This model object is holding data for the respective view model.
var model = new MyModel(); 
 
// Populate the model object with the data.
model.property1 = value; etc...


// Initiate the chain of responsibility
EnumObjectSelector primaryRule = new PrimaryRule();

var desiredModelObject = primaryRule.ExecuteRule(model);

To summarise :
1. We converted all if to Rules.
2. We created EnumObjectSelector the class representing chain of responsibility.
3. We created ExecuteRule method which takes care of executing next/successor rule.
4. We created PrimaryRule and added a constructor. Here we created a linkedlist / chains of rules in the respective order of execution.
5. In the main class where our business logic resides, where we have all the if loops, we invoke the chain of responsibility.

Thus we have successfully replaced all if loops with just one line. If at a later stage we see a scope for addition/updation/deletion of any rules, we just have to modify our "ParentObjectSelector" class by adding new or updating the existing rules.

Thank you for visiting my blog and I hope it was useful. I recommend you to go through "SourceMaking" or "Refactoring Guru" for learning other design patterns.

Please leave your comments and valuable feedback to help me improve and contribute more. You can also visit my Github repo (https://github.com/89Ash) which I'm building with some sample projects.