Composite Design Pattern

In this post I’ll demonstrate the Composite Design Pattern. This is the next post in the Design Pattern Series which falls under the category of Structural Design Patterns. In the previous post we looked at the Builder Design Pattern which is used to build a complex or a composite object using a series of steps. Composite Design Pattern is very helpful when we have a tree structure and there is a need to treat the parent as well as child object in the same manner.

Problem Statement

Assume we are building software for a renowned retailer. The retailer has stores across the country. We need to calculate the profit for each City where the retailer operates. Also the profits need to be calculated at the State level. We can extend this example further by saying that various states can be grouped together into regions and so forth. For simplicity we will stop at the State level.

Profit Calculator Without Composite Pattern

Lets start with defining the domain objects related to this problem statement. We can map the requirement to different classes like Store, City and State. City class will contain a list of Stores within that city. Similarly State class will contain a list of States. We start with the simplest class Store.

    public class Store

    {

        public int Id { get; set; }

 

        public string Name { get; set; }

 

        public int Profit { get; set; }

    }

The Store class defined above is very simple and self explanatory. To keep things simple, we assume that the Profit at Store level is computed using some complex financial calculations which are outside the scope of this post. We don’t bother about how it is calculated, but we know that we’ll be able to calculate this value by some means. Lets move on to the City class which has a collection of Stores.

    public class City

    {

        public City()

        {

            CityStores = new List<Store>();

        }

 

        public int Id { get; set; }

 

        public string Name { get; set; }

 

        public IList<Store> CityStores { get; private set; }

 

        public void AddStore(Store store)

        {

            CityStores.Add(store);

        }

 

        public void RemoveStore(Store store)

        {

            CityStores.Remove(store);

        }

 

        public int GetCityProfit()

        {

            int profit = 0;

 

            foreach (Store store in CityStores)

            {

                profit += store.Profit;

            }

 

            return profit;

        }

    }

We have a CityStores collection which is manipulated using the AddStore and RemoveStore methods. We also have the GetCityProfit method which iterates all the stores and adds up the profits for them. Similarly we have the State class which is almost the same but operates at the City level while aggregating the profits.

    public class State

    {

        public State()

        {

            Cities = new List<City>();

        }

 

        public int Id { get; set; }

 

        public string Name { get; set; }

 

        public IList<City> Cities { get; private set; }

 

        public void AddCity(City city)

        {

            Cities.Add(city);

        }

 

        public void RemoveCity(City city)

        {

            Cities.Remove(city);

        }

 

        public int GetStateProfit()

        {

            int profit = 0;

 

            foreach (City city in Cities)

            {

                foreach (Store store in city.CityStores)

                {

                    profit += store.Profit;

                }

            }

 

            return profit;

        }

    }

AddCity and RemoveCity methods help in managing the list of Cities related to a particular State. The GetStateProfit method iterates over two collections. The first iteration is for the cities within the state and the next loop is for all the stores within a city.

Limitations of this approach

As we can see from the above code for every level of hierarchy we have additional looping to do in order to compute the profit. For example imagine what will happen if we were to calculate the profit at the regional level by combining multiple states together. In another scenario imagine if we have a big metropolitan city which we wish to split further into smaller groups.

There exists a hierarchy between a top level element and its children like State and City and also City and individual Store elements. All that we are doing is performing similar operation be it at the parent level or the child level. All this process could be simplified if we can treat the parent which contains a list of child objects and the child nodes itself in the same manner. This is exactly the kind of situation tailor made for implementing the composite design pattern.

Refactoring towards Composite Pattern

The crux of the composite pattern is that we can treat the composite object and the individual leaf object in exactly the same way. In order to do that we can define an interface which can be implemented by both the parent and the child nodes.

    public interface IProfitable

    {

        int GetProfit();

 

        void AddChild(IProfitable child);

 

        void RemoveChild(IProfitable child);

    }

The IProfitable interface defines a method for getting the profit and to manage the child elements. All the interested objects will implement this interface. This interface definition seems ok for those objects which have child elements associated with them like State or City. The problem comes if we were to implement the same interface at the leaf node level which happens to be Store in our context. Lets see the Store class implementation.

    public class Store : IProfitable

    {

        public int Profit { get; set; }

 

        public int GetProfit()

        {

            return Profit;

        }

 

        public void AddChild(IProfitable city)

        {

            throw new NotImplementedException();

        }

 

        public void RemoveChild(IProfitable city)

        {

            throw new NotImplementedException();

        }

    }

We implement only the methods of IProfitable which are relevant at the leaf level like GetProfit in this case. GetProfit simply returns the value of Profit property. The methods related to child management AddChild and RemoveChild are not implemented and throw exception if the client tries to invoke these methods. Lets continue to the City class implementation.

    public class City : IProfitable

    {

        public City()

        {

            Stores = new List<IProfitable>();

        }

 

        public IList<IProfitable> Stores { get; private set; }

 

        public int GetProfit()

        {

            int profit = 0;

 

            foreach (IProfitable store in Stores)

            {

                profit += store.GetProfit();

            }

 

            return profit;

        }

 

        public void AddChild(IProfitable store)

        {

            if (store is Store)

            {

                Stores.Add(store);

            }

        }

 

        public void RemoveChild(IProfitable store)

        {

            Stores.Remove(store);

        }

    }

In case of City class the code is mostly the same as it was previously with the exception that we are referring the list as well as the parameters to the AddChild and RemoveChild methods using the IProfitable interface. Because of this we have to add the type cheking logic in the AddChild and RemoveChild methods to make sure the type of parameter which is passed is Store. Now lets look at the refactored Sate class

    public class State : IProfitable

    {

        public State()

        {

            Cities = new List<IProfitable>();

        }

 

        public IList<IProfitable> Cities { get; private set; }

 

        public int GetProfit()

        {

            int profit = 0;

 

            foreach (IProfitable city in Cities)

            {

                profit += city.GetProfit();

            }

 

            return profit;

        }

 

        public void AddChild(IProfitable city)

        {

            if (city is City)

            {

                Cities.Add(city);

            }

        }

 

        public void RemoveChild(IProfitable city)

        {

            Cities.Remove(city);

        }

    }

The State class is almost identical to City class. The difference comes in the implementation of the GetProfit method. We are no longer looping multiple times. We have only one level of looping which iterates over the child elements calling their respective GetProfit method. You can imagine how simple it would be to accommodate the two scenarios described above that of having regional profit within metropolitan city of profit at the regional level comprising multiple states. Also note that we no longer have methods like GetStateProfit or GetStateProfit. I agree that this discrepancy in names could have been solved in earlier implementation as well by using the same name GetProfit for all the classes.

Conclusion

Composite design pattern helps us to treat the composite and individual objects in the unified manner. It is used in situations where the part-whole hierarchies are used in code structure resulting in tree structure.

Many people prefer not to add the AddChild and RemoveChild or similar methods related only to the composite object to the interface. If we take this approach there is no need to throw exceptions from the leaf node class like we did for Store class. The composites can inherit from an abstract class which has the methods specific to composite objects.

As always the complete source code is available for download Composite Design Pattern Demo.zip.

Until next time Happy Programming.

Further Reading

Here are some books I recommend based on the topic discussed in this blog.

Share:
spacer

6 comments:

  1. Good work i like it very much and thanks for sharing this .
    Wemaketheapps

    ReplyDelete
  2. Hi, is there a real performance benefits in using the composite pattern in your example?
    I can see there is one less loop but still the second loop is done in the child class.
    Please could you explain this part ?

    ReplyDelete
  3. Vivek: Very good article on composite pattern.

    ReplyDelete
  4. Please show output also.

    ReplyDelete
  5. It's interesting that many of the bloggers your tips helped to clarify a few things for me as well as giving.Most of ideas can be nice content.Sometimes you just have to yell at people and give them a good shake to get your point across.
    Being new to the blogging world I feel like there is still so much to learn.Your tips helped to clarify a few things for me as well as giving.

    Informatica training in chennai

    ReplyDelete
  6. This comment has been removed by a blog administrator.

    ReplyDelete