Decorator Design Pattern

This post is the continuation of the Design Patterns series. In this post I’ll demonstrate the Decorator Design Pattern. Decorator is a structural design pattern. It is used when we want to dynamically assign responsibility to an object at runtime. Lets look at an example.

Problem Statement

Lets assume we are building software for a store specializing in selling electronic items. Customers can walk into the store and pick the items themselves. Alternately they can also reserve an item over the internet and collect it themselves from the store or have it shipped to their preferred address with an additional delivery charges. Lets build this functionality.

    public class OrderManager

    {

        public OrderManager()

        {

            Order simpleOrder = new Order { Amount = 100, RequiresShipping = false };

 

            int totalAmount = simpleOrder.GetTotalAmount();

 

            Console.WriteLine("Total amount payable : {0}", totalAmount);

 

            Order orderWithShipping = new Order { Amount = 100, RequiresShipping = true };

 

            totalAmount = orderWithShipping.GetTotalAmount();

 

            Console.WriteLine("Total amount payable : {0}", totalAmount);

        }

    }

We have a very simple Order class  which has two properties for storing the Amount and the Boolean flag whether the order requires shipping or not. If RequiresShipping attribute is set to true then we add 10 to the Amount.

Assume that the store is making good business and the online orders have grown exponentially. So the management decides to have premium deliveries which are similar to those orders with shipping, but the exception is that the customer can decide what time slot he wishes to have the delivery. For example, the customer can select a predefined slot like between 5-7 PM on Saturday evening. While the premium delivery customer can select this option, customers choosing normal delivery will be not have such flexibility and can expect the products to be shipped anytime during the day. Lets bake this functionality into the system.

            Order premiumDelivery = new Order { Amount = 100, RequiresShipping = true, IsPremiumDelivery = true };

 

            totalAmount = premiumDelivery.GetTotalAmount();

 

            Console.WriteLine("Total amount payable : {0}", totalAmount);

So in this case we added a new Boolean property to identify if the shipping is premium delivery and added another 10 to the amount along with the shipping charges.

Twist in the tale

After some time the store expands its business model to support international customers. Now we need to add flat 50 to the order amount. Along with the international deliveries, the store is also panning to offer the personalization service to customers. They can have some text engraved on the electronic item. This would cost the customers additional 5 bucks. Customers can also opt for gift wrapping the shipped items at an additional cost of 15 bucks. How do we build these additional requirements into the software?

With the added functionalities, we can have lots and lots of permutations and combinations. Lets take two examples. First one is a domestic customer opting for a premium delivery slot with personalized message and gift wrapping. The second case is international customer with just the gift wrapping. As we can see there can be various other combinations here based on customers choice. The problem with the current solution is that every new requirement will force us to modify the existing code.

Refactoring towards Decorator Design Pattern

If we can come up with a functionality common to all the cases and then add the specific cases it would be really helpful. Since we don’t know beforehand what choices will be made by the user at runtime we need to be flexible enough to ensure various combinations are addressed. This is where Decorator pattern helps us. We can extract the lowest common functionality into an interface or a abstract class. And then the specific cases can override their behaviour by inheriting from the base class or implementing the interface. The way Decorator helps us in adding the functionality at runtime is by composing the objects. Lets look at the implementation to understand this better.

    public interface IOrder

    {

        int GetTotalAmount();

    }

We start with the simplest interface IOrder, which defines the common functionality. In our case an Order is the common thing we have in all cases. The implementation of simple order is very straightforward.

    public class Order : IOrder

    {

        public int Amount { get; set; }

 

        public int GetTotalAmount()

        {

            return Amount;

        }

    }

Nest step is to factor out the dynamic things. We extract this into an abstract class taken an existing order and also implements the IOrder interface. We call this class as OrderDecorator.

    public abstract class OrderDecorator : IOrder

    {

        protected readonly IOrder _order;

 

        protected OrderDecorator(IOrder order)

        {

            _order = order;

        }

 

        public abstract int GetTotalAmount();

    }

Note that the GetTotalAmount method is marked as abstract forcing the derived classes to override it. We start by looking at the LocalShippingOrderDecorator class which implements the domestic shipping functionality.

    public class LocalShippingOrderDecorator : OrderDecorator

    {

        private const int LOCALSHIPPINGCHARGES = 10;

 

        public LocalShippingOrderDecorator(IOrder order)

            : base(order)

        {

        }

 

        public override int GetTotalAmount()

        {

            return _order.GetTotalAmount() + LOCALSHIPPINGCHARGES;

        }

    }

It is straightforward. The overridden GetTotalAmount method delegates the call to the instance of Order class passed in as constructor argument  and also adds the local shipping charges. By doing so we have simplified our design. This confirms to the Single Responsibility Principle (SRP). Our Order class no longer needs to know whether it is for domestic or international shipping. The concerns are separated out to distinct classes. This design also helps us in the Open Close Principle (OCP). As we can see from the next code snippet, we can add InternationalOrderShippingDecorator class can cater to the international customers without impacting local orders.

    public class InternationalOrderShippingDecorator : OrderDecorator

    {

        private const int INTERNATIONALSHIPPINGCHARGES = 50;

 

        public InternationalOrderShippingDecorator(IOrder order)

            : base(order)

        {

        }

 

        public override int GetTotalAmount()

        {

            return _order.GetTotalAmount() + INTERNATIONALSHIPPINGCHARGES;

        }

    }

I’ll avoid displaying the GiftWrapOrderDecorator to save some space here. You can have a look at it in the attached solution file. Next part is bit interesting. Now consider our first case described above. How do we use these classes to fulfil the first case?

            // First case - Domestic customer with premium delivery, personalized message and gift wrap

            IOrder order = new Order { Amount = 100 };

 

            IOrder premiumDelivery = new PremiumOrderShippingDecorator(order);

 

            IOrder giftWrappedOrder = new GiftWrapOrderDecorator(premiumDelivery);

 

            int totalAmount = giftWrappedOrder.GetTotalAmount();

 

            Console.WriteLine("Total amount payable : {0}", totalAmount);

We start by creating the Order and passing it into the premium delivery decorator which is then passed onto the gift wrapper. And finally we invoke the GetTotalAmount on this instance. Instead of creating individual instances and passing them to the decorator classes, we can also choose to go with recursive composition and build the required chain as shown below which caters to the second scenario

            // Second case - International customer with gift wrapping

            IOrder internationalCustomer =

                new InternationalOrderShippingDecorator(new GiftWrapOrderDecorator(new Order { Amount = 100 }));

 

            totalAmount = internationalCustomer.GetTotalAmount();

 

            Console.WriteLine("Total amount payable : {0}", totalAmount);

We have avoided storing the decorators into variables by directly passing them to the constructor. This has the same impact as above.

Examples of decorators within DOTNET framework

There are various examples of Decorator pattern within DOTNET framework. The most common example is the Streams in System.IO namespace. We have the abstract Stream class which acts as the base class for many other streams. We can say that BufferedStream as a classic example of Decorator implementation as its constructor takes an instance of a Stream class and also the BufferedStream inherits from Stream class itself. You can also look at the CryptoStream class which decorates the Stream. DeflateStream and GZipStream classes from System.IO.Compression namespace are also examples of Stream decorators.

Conclusion

Decorator Design Pattern is very handy when we want to dynamically assign responsibility to an object. It can be used to dynamically assign state to an object or to dynamically assign behaviour to an object. The example we saw here was that of dynamically assigning the behaviour to the object. If we correlate the objects  used here in the example with the terminology used within Gang of Four book, our IOrder interface becomes the component and OrderDecorator is the decorator. Order is the concrete component. And all the classes implementing the OrderDecorator abstract class are the concrete Decorators. I have not implemented the PersonalizedMessageOrderDecorator. I leave it as an exercise for readers to implement it.

The advantage of using Decorator is that the class which is being decorated like the Order doesn’t need to know that it is being decorated by some other class. This encourages loose coupling. The component as well as decorator can vary independently.

As always the complete working solution is available for DecoratorDesignPatternDemo.zip.

Until next time Happy Programming.

Further Reading

Here are some books I recommend related to the topics discussed in this blog.

Share:
spacer

2 comments:

  1. Very nice example. Thanks a lot !!

    ReplyDelete
  2. Find an excellent example here as well

    http://zishanbilal.com/2011/04/28/design-patterns-by-examples-decorator-pattern/

    ReplyDelete