Friday 17 December 2010

Decorator Pattern

"Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to sub-classing for extending functionality." 



If client code is using an implementation class that implements Component and it is provided by a class dispenser (e.g. an IoC container or Factory) then that code could initially be calling ConcreteComponent's Operation() through the Component abstract class or interface.  Because Decorators implement the same interface/abstract class as ConcreteComponent we could make our class dispenser substitute a Decorator instance in place of ConcreteComponent e.g. ConcreteDecoratorB and hand that out instead.  ConcreteDecoratorB's implementation of Operation() includes a call to AddedBehavior() after it has called its member Component's Operation().  From the client's perspective it is now calling Operation() on a Component and getting AddedBehavior() as well. 
The key to the Decorator pattern is that the decorator both implements (is-a) and aggregates (has-a) the component.
Because concrete decorators and concrete components have the same base class/interface the really dynamic aspect of this pattern is provided by the ability for decorators to nest concrete components or other decorators. This leads to much more flexible configurations than would be the case with rigid inheritance hierarchies.
In my code example we start with a CarrotCake and then we "decorate" it with icing, with a candle and with both. If we were to try to use inheritance we would end up with rigid hierarchies.  We would also end up with a problem if we were to inherit CarrotCakeWithIcing from CarrotCake and CarrotCakeWithACandle from CarrotCake. What do we do if we want a CarrotCakeWithIcingAndACandle? - NB. we dont have multiple inheritance in C#...
namespace Decorator
{
    class Program
    {
        static void Main(string[] args)
        {
            Cake c;
 
            Console.WriteLine("--- Carrot Cake ---");
            c = CakeFactory.CreateCake("CarrotCake");
            c.DisplayCake();
 
            Console.WriteLine("--- Carrot Cake With Icing---");
            c = CakeFactory.CreateCake("CarrotCakeWithIcing");
            c.DisplayCake();
 
            Console.WriteLine("--- Carrot Cake With a Candle---");
            c = CakeFactory.CreateCake("CarrotCakeWithACandle");
            c.DisplayCake();
 
            Console.WriteLine(
               "--- Carrot Cake With Icing and a Candle---");
            c = CakeFactory.CreateCake(
               "CarrotCakeWithIcingAndACandle");
            c.DisplayCake();
         
            Console.ReadLine();
        }
    }
 
    // Component
    public abstract class Cake
    {
        public abstract void DisplayCake();
    }
 
    // ConcreteComponent
    public class CarrotCake : Cake
    {
        public override void DisplayCake()
        {
             Console.WriteLine("Carrot cake");
        }
    }
 
    // Decorator
    public abstract class CakeDecorator : Cake
    {
        private Cake _cake;
 
        public CakeDecorator(Cake cake)
        {
           _cake = cake;
        }
 
        public override void DisplayCake()
        {
           _cake.DisplayCake();
        }     
    }
 
    // Concrete Decorator
    public class CarrotCakeWithIcing : CakeDecorator
    {
        public CarrotCakeWithIcing(Cake cake) :
            base( cake)
        {
        }
 
        public override void DisplayCake()
        {
            base.DisplayCake();
            AddedBehaviour();
        }
 
        private static void AddedBehaviour()
        {
            Console.WriteLine("... with buttercream.");
        }
    }
 
    // Concrete Decorator
    public class CarrotCakeWithACandle : CakeDecorator
    {
        public CarrotCakeWithACandle(Cake cake) :
            base(cake)
        {
        }
 
        public override void DisplayCake()
        {
            base.DisplayCake();
            AddedBehaviour();
        }
 
        private static void AddedBehaviour()
        {
            Console.WriteLine("... with a candle on top.");
        }
    }
 
    // Creates Cakes
    public class CakeFactory
    {
        public static Cake CreateCake(string cakeDescription)
        {
            if (cakeDescription == "CarrotCake")
            {
                return new CarrotCake();
            }
            else if (cakeDescription == "CarrotCakeWithIcing")
            {
                return new CarrotCakeWithIcing(new CarrotCake());
            }
            else if (cakeDescription == "CarrotCakeWithACandle")
            {
                return new CarrotCakeWithACandle(new CarrotCake());
            }
            else if (cakeDescription == "CarrotCakeWithIcingAndACandle")
            {
                return new CarrotCakeWithACandle(
                       new CarrotCakeWithIcing(
                       new CarrotCake()));
            }
 
            return null;
        }
    }
}


Outputs:

--- Carrot Cake ---
Carrot cake
--- Carrot Cake With Icing---
Carrot cake
... with buttercream.
--- Carrot Cake With a Candle---
Carrot cake
... with a candle on top.
--- Carrot Cake With Icing and a Candle---
Carrot cake
... with buttercream.
... with a candle on top.

 

No comments:

Post a Comment