http://www.codinghelmet.com/  

Wear a helmet. Even when coding.

howto > reduce-cyclomatic-complexity-composite-design-pattern

How to Reduce Cyclomatic Complexity: Composite Design Pattern
by Zoran Horvat @zoranh75

In the previous article in this series we have demonstrated one example in which the code could be simplified and its cyclomatic complexity reduced, by removing the collection from a class and wrapping it in a dedicated class. Composite design pattern provided means of moving the collection into another object (see How to Reduce Cyclomatic Complexity: Working With Collections for details). That was more of an academic example, and in that is why we will revisit the Composite design pattern idea in this article again, this time in a more realistic setting.

Real World Composite Example

In this series of texts on techniques to reduce cyclomatic complexity we have been refactoring one e-commerce application. One of the requirements for this application was that users can achieve discounts. When user spends more than $100, she is eligible for a 5% discount on all subsequent purchases. When user brings in another user, she receives a 2% discount for all subsequent purchases.

In the first implementation, User class was responsible to keep record of the discounts:

namespace Store.Domain.Implementation
{
    public class User: IRegisteredUser
    {
        private decimal totalPurchases;
        private bool hasReceivedLoyaltyDiscount;
        private IList<IDiscount> discounts = new List<IDiscount>();

        public IReceiptViewModel Purchase(IProduct item)
        {
            IProduct discountedItem = item.ApplyDiscounts(this.discounts);
            this.RegisterPurchase(discountedItem.Price);
            return new ReceiptDto(this, discountedItem.Name, discountedItem.Price);
        }

        private void RegisterPurchase(decimal price)
        {
            this.totalPurchases += price;
            if (!hasReceivedLoyaltyDiscount && this.totalPurchases > 100.0M)
            {
                this.discounts.Add(new Discount(0.05M));
                this.hasReceivedLoyaltyDiscount = true;
            }
        }

        public void ReferralAdded()
        {
            this.discounts.Add(new Discount(.02M));
        }

        ...
    }
}

As you can see from this implementation, almost all the logic regarding discounts is concentrated inside the User class. The only part of the logic that remains is applying discounts to reach a lower price of an item. That piece of code was put in the Product class:

namespace Store.Domain.Implementation
{
    public class Product: IProduct
    {
        public decimal Price { get; private set; }

        ...

        public IProduct ApplyDiscounts(IEnumerable<IDiscount> discounts)
        {
            decimal price = this.Price;

            foreach (IDiscount discount in discounts)
                price = discount.Apply(price);

            return new Product(this.Name)
            {
                Price = price
            };
        }

    }
}

ApplyDiscounts method iterates through the discounts and applies them in a row. The price reached in the end is the discounted price.

Note one important detail in this implementation. Product class takes care about the case when discounts collection is empty. This is extremely important detail. We certainly wouldn’t like to have Product class explode when user is not eligible for any discount. What this method does is to return actual price of the product when no discount is available.

This part of code, both in the User and in the Product classes, can now be subjected to a refactoring. Goal of this refactoring is to move collection of discounts into a separate class which implements IDiscount abstract discount interface.

Preparing For the Composite

Very often in practice, it is not easy to just pull the collection out and replace it with the composite element. Some infrastructure work needs to precede it to make composite fit more naturally.

In the e-commerce application, the first refactoring step was to move out the logic which decides when the user is eligible for a certain discount (see How to Reduce Cyclomatic Complexity: Domain Logic in Factories for details.) After this logic was moved out, the User class became much easier to follow:

namespace Store.Domain.Implementation
{
    public class User : IRegisteredUser
    {
        public string UserName { get; private set; }
        private IList<IDiscount> discounts = new List<IDiscount>();
        private DiscountRules discountRules;

        public User(string userName)
        {
            this.UserName = userName;
            this.discountRules = new DiscountRules();
        }

        public IReceiptViewModel Purchase(IProduct item)
        {
            IProduct discountedItem = item.ApplyDiscounts(this.discounts);
            this.RegisterPurchase(discountedItem.Price);
            return new ReceiptDto(this, discountedItem.Name, discountedItem.Price);
        }

        private void RegisterPurchase(decimal price)
        {
            this.discountRules
                .MoneySpent(price)
                .TryCreate()
                .Each(discount => this.discounts.Add(discount));
        }

        private void ReferralAdded()
        {
            this.discountRules
                .ReferralAdded()
                .TryCreate()
                .Each(discount => this.discounts.Add(discount));
        }

        ...
    }
}

After this modification, we are ready to use the Composite design pattern to simplify the code.

The first step is to make the Product class expect a single abstract discount instead of the collection:

namespace Store.Domain.Implementation
{
    public class Product: IProduct
    {
        public decimal Price { get; private set; }

        ...

        public IProduct Apply(IDiscount discount)
        {
            return new Product(this.Name)
            {
                Price = discount.Apply(this.Price)
            };
        }

    }
}

This is serious simplification, because the product doesn’t have to worry about how the discounts are supposed to be combined. Some discounts could exclude each other, for example. Product class didn’t take this kind of business rules into account. With abstract discount defined, Product is free to deal with its own responsibilities and leave discounts logic to someone else.

The next step is to extend the IDiscount interface to support adding more discounts to the collection:

namespace Store.Application
{
    public interface IDiscount
    {
        decimal Apply(decimal price);
        IDiscount CombineWith(IDiscount discount);
    }
}

This is an example where domain logic finds its way to the abstract interface. Abstract discount is now aware of such concept as combining two discounts into one. Previously, while User and Product classes used to control discounts, discount itself was oblivious of the fact that it could be combined with some other discounts.

Final preparation step is to make sure that User only contains one discount:

namespace Store.Domain.Implementation
{
    public class User : IRegisteredUser
    {
        private IDiscount discount;
        private DiscountRules discountRules;

        public IReceiptViewModel Purchase(IProduct item)
        {
            IProduct discountedItem = item.Apply(this.discount);
            this.RegisterPurchase(discountedItem.Price);
            return new ReceiptDto(this, discountedItem.Name, discountedItem.Price);
        }

        public void SetReferrer(IRegisteredUser referrer)
        {
            this.referrer = referrer;
            referrer.ReferralAdded();
        }

        private void RegisterPurchase(decimal price)
        {
            this.AddDiscount(
                this.discountRules
                    .MoneySpent(price));
        }

        private void ReferralAdded()
        {
            this.AddDiscount(
                this.discountRules
                    .ReferralAdded());
        }

        private void AddDiscount(IDiscountFactory discountFactory)
        {
            this.discount =
                discountFactory
                .TryCreate()
                .Select(newDiscount => this.discount.CombineWith(newDiscount))
                .DefaultIfEmpty(this.discount)
                .Single();
        }
    }
}

With this change, all elements in the system are aware of only one discount. This discount is responsible to combine simple discounts when it comes to combining them.

We are finally ready to implement the concrete composite element.

Implementing the Composite Pattern

Now it only remains to provide concrete implementations of the IDiscount interface. These concrete implementations should cover all the use cases, including the one when there are multiple discounts assigned to the same user.

The simplest case we have to support is when the user doesn’t have any discounts:

namespace Store.Domain.Implementation
{
    internal class NoDiscount: IDiscount
    {
        public decimal Apply(decimal price)
        {
            return price;
        }

        public IDiscount CombineWith(IDiscount discount)
        {
            return discount;
        }
    }
}

This class makes no changes to the product to which it is applied. Price remains the same. When combined with any other discount, that other discount is the sole result. This means that this empty discount doesn’t interfere with other discounts either.

The second case we have to cover is a simple discount, one that just does the real work of reducing the price:

namespace Store.Domain.Implementation
{
    public class Discount: IDiscount
    {

        private decimal relativeDiscount;

        public Discount(decimal relativeDiscount)
        {
            this.relativeDiscount = relativeDiscount;
        }

        public decimal Apply(decimal price)
        {
            return price * (1.0M - this.relativeDiscount);
        }

        public IDiscount CombineWith(IDiscount discount)
        {
            return new ComplexDiscount(new IDiscount[] { this, discount });
        }
    }
}

When it comes to combining the simple discount with another discount, this object augments itself to a ComplexDiscount instance. This is how we can add more than one discount to the system. Simple discount would just return a complex discount as the result.

Final class which concludes the Composite design pattern implementation in this project is the ComplexDiscount:

namespace Store.Domain.Implementation
{
    internal class ComplexDiscount : IDiscount
    {

        private IEnumerable<IDiscount> discounts;

        public ComplexDiscount(IEnumerable<IDiscount> discounts)
        {
            this.discounts = new List<IDiscount>(discounts);
        }

        public decimal Apply(decimal price)
        {
            return this.discounts
                .Aggregate(price, (curPrice, discount) => discount.Apply(curPrice));
        }

        public IDiscount CombineWith(IDiscount discount)
        {
            return new ComplexDiscount(
                this.discounts
                    .Union(new IDiscount[] { discount }));
        }
    }
}

This class doesn’t apply discounts to prices. But it does all the work of combining multiple discounts to a single price. Net result is that now we have three classes that cover three features – having no discounts, having one discount, and having several discounts that have to be combined to produce a final price.

Final touch in this solution is to properly initialize the User class:

namespace Store.Domain.Implementation
{
    public class User : IRegisteredUser
    {
        public string UserName { get; private set; }
        private IDiscount discount;
        private IBuyer referrer;
        private DiscountRules discountRules;

        public User(string userName)
        {
            this.UserName = userName;
            this.discount = new NoDiscount();
            this.discountRules = new DiscountRules();
        }
        ...
    }
}

In the constructor, User class initializes its discount to NoDiscount instance. This perfectly models the business rule that user initially has no discounts. Any discounts that could be added to the user are combined with this initial discount to produce any list or even hierarchy of discounts that comes to be.

Conclusion

In this article we have seen one realistic applications of the Composite design pattern. When Composite is applied, scaffolding code is wrapped in a class which we normally never read after it is written. Domain-related code remains in domain classes, relieved from the scaffolding code, and therefore easier to read and to understand.

See also:

Published: Jun 23, 2015

ZORAN HORVAT

Zoran is software architect dedicated to clean design and CTO in a growing software company. Since 2014 Zoran is an author at Pluralsight where he is preparing a series of courses on object-oriented and functional design, design patterns, writing unit and integration tests and applying methods to improve code design and long-term maintainability.

Follow him on Twitter @zoranh75 to receive updates and links to new articles.

Watch Zoran's video courses at pluralsight.com (requires registration):

Making Your C# Code More Object-Oriented

This course will help leverage your conceptual understanding to produce proper object-oriented code, where objects will completely replace procedural code for the sake of flexibility and maintainability. More...

Advanced Defensive Programming Techniques

This course will lead you step by step through the process of developing defensive design practices, which can substitute common defensive coding, for the better of software design and implementation. More...

Tactical Design Patterns in .NET: Creating Objects

This course sheds light on issues that arise when implementing creational design patterns and then provides practical solutions that will make our code easier to write and more stable when running. More...

Tactical Design Patterns in .NET: Managing Responsibilities

Applying a design pattern to a real-world problem is not as straight-forward as literature implicitly tells us. It is a more engaged process. This course gives an insight to tactical decisions we need to make when applying design patterns that have to do with separating and implementing class responsibilities. More...

Tactical Design Patterns in .NET: Control Flow

Improve your skills in writing simpler and safer code by applying coding practices and design patterns that are affecting control flow. More...

Writing Highly Maintainable Unit Tests

This course will teach you how to develop maintainable and sustainable tests as your production code grows and develops. More...

Improving Testability Through Design

This course tackles the issues of designing a complex application so that it can be covered with high quality tests. More...

Share this article

webmasters