Exploring Decorator Pattern in C#

Introduction

Design patterns play a pivotal role in software development, offering tried-and-tested solutions to recurring problems. Among these patterns, the Decorator Pattern stands out as a versatile tool for extending the functionality of classes without altering their structure. In this article, we will delve into the Decorator Pattern and how it can be implemented in C#.

What is the Decorator Pattern?

The Decorator Pattern is a structural design pattern that allows you to add behavior or responsibilities to individual objects dynamically without modifying their code. It is part of the Gang of Four (GoF) design patterns and promotes the principle of open-closed design, where classes are open for extension but closed for modification.

At its core, the Decorator Pattern involves a set of decorator classes that are used to wrap concrete components. These decorators add new behaviors or modify existing ones, creating a chain of decorators that can be stacked on top of one another. The final result is an object with a combination of behaviors from both the original component and its decorators.

Components of the Decorator Pattern

To understand the Decorator Pattern better, let's break it down into its main components:

  1. Component Interface: This defines the interface or abstract class that all concrete components and decorators must implement. It represents the base object that will be decorated.
  2. Concrete Component: This is the actual object that implements the component interface. It represents the core functionality that may be extended.
  3. Decorator: Decorators are abstract classes that also implement the component interface. They maintain a reference to a component object and add additional behavior or state to it.
  4. Concrete Decorator: These are the concrete subclasses of decorators. They extend the functionality of the component and can be stacked to create complex combinations of behaviors.

Implementing the Decorator Pattern in C#

Let's explore a simple example of the Decorator Pattern in C# by creating a coffee ordering system.

Step 1. Define the Component Interface

public interface ICoffee
{
    string GetDescription();
    double GetCost();
}

Step 2. Create a Concrete Component

public class SimpleCoffee : ICoffee
{
    public string GetDescription()
    {
        return "Simple Coffee";
    }

    public double GetCost()
    {
        return 1.0;
    }
}

Step 3. Create a Decorator

public abstract class CoffeeDecorator : ICoffee
{
    protected ICoffee _coffee;

    public CoffeeDecorator(ICoffee coffee)
    {
        _coffee = coffee;
    }

    public virtual string GetDescription()
    {
        return _coffee.GetDescription();
    }

    public virtual double GetCost()
    {
        return _coffee.GetCost();
    }
}

Step 4. Create Concrete Decorators

public class MilkDecorator : CoffeeDecorator
{
    public MilkDecorator(ICoffee coffee) : base(coffee)
    {
    }

    public override string GetDescription()
    {
        return $"{base.GetDescription()}, with Milk";
    }

    public override double GetCost()
    {
        return base.GetCost() + 0.5;
    }
}

public class SugarDecorator : CoffeeDecorator
{
    public SugarDecorator(ICoffee coffee) : base(coffee)
    {
    }

    public override string GetDescription()
    {
        return $"{base.GetDescription()}, with Sugar";
    }

    public override double GetCost()
    {
        return base.GetCost() + 0.2;
    }
}

Step 5. Putting It All Together

class Program
{
    static void Main(string[] args)
    {
        ICoffee coffee = new SimpleCoffee();
        Console.WriteLine($"Description: {coffee.GetDescription()}");
        Console.WriteLine($"Cost: ${coffee.GetCost()}");

        coffee = new MilkDecorator(coffee);
        Console.WriteLine($"Description: {coffee.GetDescription()}");
        Console.WriteLine($"Cost: ${coffee.GetCost()}");

        coffee = new SugarDecorator(coffee);
        Console.WriteLine($"Description: {coffee.GetDescription()}");
        Console.WriteLine($"Cost: ${coffee.GetCost()}");
    }
}

Step 6. Running the Code

When you run the code, you will see how decorators are used to dynamically add functionality to the simple coffee object.

Description: Simple Coffee
Cost: $1
Description: Simple Coffee, with Milk
Cost: $1.5
Description: Simple Coffee, with Milk, with Sugar
Cost: $1.7

Conclusion

The Decorator Pattern is a powerful tool for enhancing the functionality of objects in a flexible and maintainable way. It allows you to add or modify behavior at runtime without altering the core classes, making your code more open for extension and closed for modification. By mastering this pattern, you can create more modular and adaptable software in C# and beyond.

Happy Learning :) 


Similar Articles