Tuesday, January 2, 2024

Decorator Design Pattern in C#

The Decorator Design Pattern is a structural pattern in software development that allows behavior to be added to individual objects, either statically or dynamically, without affecting the behavior of other objects from the same class.

The idea of the Decorator Pattern is to wrap an existing class, add other functionality to it, then expose the same interface to the outside world. Because of this our decorator exactly looks like the original class to the people who are using it.

It is used to extend or alter the functionality at runtime. It does this by wrapping them in an object of the decorator class without modifying the original object. So it can be called a wrapper pattern.

Components of Decorator Design Pattern

  • Component: It defines the interface of the actual object that needs functionality to be added dynamically to the ConcreteComponents.
  • ConcreteComponent: The actual object in which the functionalities could be added dynamically.
  • Decorator: This defines the interface for all the dynamic functionalities that can be added to the ConcreteComponent.
  • ConcreteDecorator: All the functionalities that can be added to the ConcreteComponent. Each needed functionality will be one ConcreteDecorator class.

Example in C#

In C#, let's illustrate the Decorator Pattern with an example involving a beverage system, where we have a base Beverage class representing different types of drinks.

  1. Component(IBeverage.cs):
    namespace DecoratorPattern.Component
    {
        /// 
        /// Interface for beverage
        /// 
        public interface IBeverage
        {
            string GetDescription();
            double Cost();
        }
    }
        
  2. Concrete Component(Coffee.cs):
    namespace DecoratorPattern.Component
    {
        /// 
        /// Concrete component representing a basic beverag
        /// 
        public class Coffee : IBeverage
        {
            public string GetDescription()
            {
                return "Coffee";
            }
    
            public double Cost()
            {
                return 5.0;
            }
        }
    }
        
  3. Decorator(BeverageBaseDecorator.cs):
    using DecoratorPattern.Component;
    
    namespace DecoratorPattern.Decorator
    {
        /// 
        /// Decorator base class
        /// 
        public abstract class BeverageBaseDecorator : IBeverage
        {
            protected IBeverage _beverage;
    
            public BeverageBaseDecorator(IBeverage beverage)
            {
                _beverage = beverage;
            }
    
            public virtual string GetDescription()
            {
                return _beverage.GetDescription();
            }
    
            public virtual double Cost()
            {
                return _beverage.Cost();
            }
        }
    }
    
        
  4. ConcreteDecorator(MilkDecorator.cs and SugarDecorator.cs):
    using DecoratorPattern.Component;
    
    namespace DecoratorPattern.Decorator
    {
        /// 
        /// Concrete decorator adding milk to the beverage
        /// 
        public class MilkDecorator : BeverageBaseDecorator
        {
            public MilkDecorator(IBeverage beverage) : base(beverage)
            {
            }
    
            public override string GetDescription()
            {
                return $"{_beverage.GetDescription()}, Milk";
            }
    
            public override double Cost()
            {
                return _beverage.Cost() + 0.5; // Milk costs 0.5
            }
        }
    }
    
      
    using DecoratorPattern.Component;
    
    namespace DecoratorPattern.Decorator
    {
        /// 
        /// Concrete decorator adding sugar to the beverage
        /// 
        public class SugarDecorator : BeverageBaseDecorator
        {
            public SugarDecorator(IBeverage beverage) : base(beverage)
            {
            }
    
            public override string GetDescription()
            {
                return $"{_beverage.GetDescription()}, Sugar";
            }
    
            public override double Cost()
            {
                return _beverage.Cost() + 0.2; // Sugar costs 0.2
            }
        }
    }
    
      
  5. Client:
        // Create a base coffee
    using DecoratorPattern.Component;
    using DecoratorPattern.Decorator;
    
    IBeverage coffee = new Coffee();
    
    // Decorate the coffee with milk
    coffee = new MilkDecorator(coffee);
    
    // Decorate the coffee further with sugar
    coffee = new SugarDecorator(coffee);
    
    // Output the description and cost of the final beverage
    Console.WriteLine($"Description: {coffee.GetDescription()}");
    Console.WriteLine($"Cost: ${coffee.Cost()}");
    Console.ReadLine();
    
        

This example demonstrates how you can dynamically add responsibilities to objects at runtime using the Decorator Pattern in C#. Each decorator transparently adds its own behavior to the component it decorates without affecting the behavior of other objects from the same class.

Output

decorator design pattern

Advantages of Decorator Pattern

  1. Adds functionality to existing objects dynamically
  2. Alternative to sub classing
  3. Flexible design
  4. Supports Open Closed Principle

The full source code is available here:

Happy coding!! 😊

No comments:

Post a Comment

^ Scroll to Top