Welcome to EMC Consulting Blogs Sign in | Join | Help

Owain Wraggs' Blog

I have now left EMC Consulting, you can subscribe to my new blog here: http://blog.endjin.com/author/owain-wragg/

How to Avoid If...Else If

The code below is responsible for setting the status of a Customer by examining various bits of data.

    public class CustomerProcess

    {

        public void UpdateStatus(Customer customer)

        {

            bool statusChanged = false;

 

            if (!customer.IsActivated && !customer.IsApproved)

            {

                customer.Status = CustomerStatus.New;

                statusChanged = true;

            }

            else if (customer.IsActivated && !customer.IsApproved)

            {

                customer.Status = CustomerStatus.Activated;

                statusChanged = true;

            }

            else if (customer.IsApproved)

            {

                customer.Status = CustomerStatus.Approved;

                statusChanged = true;

            }

 

            if (statusChanged)

            {

                Persist(customer);

            }

        }

 

        public void Persist(Customer customer)

        {

            //Code to persist customer

        }

    }

This a simple example and a method like this would quickly get very complex, hard to read and write unit tests for.

To improve this code we need to simplify the UpdateStatus method, reduce its responsibilities and make it simple to add or change the logic which sets the Customer’s status all in a testable way. In this scenario an approach is to use the Chain of Responsibility pattern; where each part of the chain is responsible for handling a single status change, notifying its caller that it has handled the request or if it does not handle the request call the next item in the chain.

Below is the ActivatedCustomerHandler class which is responsible for setting the Customer’s status to Activated.

    public class ActivatedCustomerHandler : CustomerHandlerBase

    {

        public ActivatedCustomerHandler(CustomerHandlerBase successor) : base(successor) { }

 

        protected override bool SetCustomerStatus(Customer customer)

        {

            bool statusSet = false;

 

            if (customer.IsActivated && !customer.IsApproved)

            {

                customer.Status = CustomerStatus.Activated;

                statusSet = true;

            }

 

            return statusSet;

        }

    }

All the SetCustomerStatus method does is set the Status to Activated if the condition in the if statement is met and return a boolean value which indicates if it set the status.

The code which decides if the next item in the chain should be called is contained in the CustomerHandlerBase class, which is the base class for all items in the chain.

    public abstract class CustomerHandlerBase

    {

        private readonly CustomerHandlerBase successor;

 

        protected CustomerHandlerBase(CustomerHandlerBase successor)

        {

            this.successor = successor;

        }

 

        public bool SetStatus(Customer customer)

        {

            bool requestHandled = SetCustomerStatus(customer);

 

            if (!requestHandled)

            {

                if (successor != null)

                {

                    requestHandled = successor.SetStatus(customer);

                }

            }

 

            return requestHandled;

        }

 

        protected abstract bool SetCustomerStatus(Customer customer);

    }

The SetStatus method does most of the work as it is responsible for calling the abstract method SetCustomerStatus checking to see if the request has been handled and if not calling the next item in the chain if one has been defined.

The next block of code shows the class which is responsible for constructing the chain and executing it.

    public class CustomerStatusHandler : ICustomerStatusHandler

    {

        public bool SetStatus(Customer customer)

        {

            CustomerHandlerBase handler = CreateChain();

 

            return handler.SetStatus(customer);

        }

 

        private CustomerHandlerBase CreateChain()

        {

            //Create the chain

            CustomerHandlerBase approvedCustomerHandler = new ApprovedCustomerHandler(null);

            CustomerHandlerBase activatedCustomerHandler = new ActivatedCustomerHandler(approvedCustomerHandler);

            CustomerHandlerBase newCustomerHandler = new NewCustomerHandler(activatedCustomerHandler);

 

            return newCustomerHandler;

        }

    }

And the final version of the CustomerProcess class

    public class CustomerProcess

    {

        private readonly ICustomerStatusHandler statusHandler;

 

        public CustomerProcess(ICustomerStatusHandler statusHandler)

        {

            this.statusHandler = statusHandler;

        }

 

        public void UpdateStatus(Customer customer)

        {

            if (statusHandler.SetStatus(customer))

            {

                Persist(customer);

            }

        }

 

        public void Persist(Customer customer)

        {

            //Code to persist customer

        }

    }

So now we have a mechanism for easily adding a new item to the chain or changing the implementation of an existing item in the chain without the chains consumer being aware that any change has taken place.

Comments

No Comments
Anonymous comments are disabled
Powered by Community Server (Personal Edition), by Telligent Systems