Here, we will see how to create Decorator Design Pattern with example.
The Decorator pattern allows behavior to be added to an individual object, dynamically, without affecting the behavior of other objects from the same class. It’s a structural pattern that uses composition rather than inheritance to extend functionality.
Example- Coffee Odering System
Imagine a coffee shop where customers can customize their coffee by adding different toppings (like milk, sugar, whipped cream, etc.). Instead of creating separate classes for every possible combination of coffee and toppings, we can use the Decorator pattern to dynamically add these toppings to a base coffee object. We will explain in detail lator part of the article.
Example - TextEditor
Imagine a TextEditor class that needs additional features like spell check and grammar check. We will aslo explain in detail lator part of the article.
Without the Decorator pattern
Imagine a TextEditor class that needs additional features like spell check and grammar check. Using inheritance would create subclasses like TextEditorWithSpellCheck, TextEditorWithGrammarCheck, and TextEditorWithBothChecks. This approach leads to many subclasses.
Without the Decorator pattern, you would typically achieve the same functionality using inheritance or by directly adding the logic to the base class. However, this can lead to some drawbacks, such as tightly coupled code, lack of flexibility, and deep inheritance hierarchies.
Here's how you can implement the functionality without the Decorator pattern.
// Base class for Text Editor
using System;
public class BasicTextEditor
{
public virtual void Display()
{
Console.WriteLine("Displaying basic text editor.");
}
}
// SpellCheck Text Editor (Extends BasicTextEditor)
public class SpellCheckTextEditor : BasicTextEditor
{
public override void Display()
{
base.Display();
Console.WriteLine("Adding spell check functionality.");
}
}
// GrammarCheck Text Editor (Extends BasicTextEditor)
public class GrammarCheckTextEditor : BasicTextEditor
{
public override void Display()
{
base.Display();
Console.WriteLine("Adding grammar check functionality.");
}
}
// Editor with Both Spell Check and Grammar Check (Inheritance Chain)
public class FullFeatureTextEditor : SpellCheckTextEditor
{
public override void Display()
{
base.Display();
Console.WriteLine("Adding grammar check functionality.");
}
}
// Usage
class Program
{
static void Main()
{
// Example of using inheritance approach for specific functionality
BasicTextEditor editor = new FullFeatureTextEditor();
editor.Display();
}
}
Output
The above approach using inheritance would create subclasses like TextEditorWithSpellCheck, TextEditorWithGrammarCheck, and TextEditorWithBothChecks. This approach leads to many subclasses. To overcome that problem you can use Decorator design pattern.
Using the Decorator pattern
// Step 1: Define the ITextEditor interface
using System;
public interface ITextEditor
{
void Display();
}
// Step 2: Basic Text Editor implementation
public class BasicTextEditor : ITextEditor
{
public void Display()
{
Console.WriteLine("Displaying basic text editor.");
}
}
// Step 3: Abstract Decorator class implementing ITextEditor
public abstract class TextEditorDecorator : ITextEditor
{
protected ITextEditor _textEditor;
public TextEditorDecorator(ITextEditor textEditor)
{
_textEditor = textEditor;
}
public virtual void Display()
{
_textEditor.Display();
}
}
// Step 4: Concrete Decorators - SpellCheck and GrammarCheck
public class SpellCheckDecorator : TextEditorDecorator
{
public SpellCheckDecorator(ITextEditor textEditor) : base(textEditor) { }
public override void Display()
{
base.Display();
Console.WriteLine("Adding spell check functionality.");
}
}
public class GrammarCheckDecorator : TextEditorDecorator
{
public GrammarCheckDecorator(ITextEditor textEditor) : base(textEditor) { }
public override void Display()
{
base.Display();
Console.WriteLine("Adding grammar check functionality.");
}
}
// Step 5: Usage of the Decorator Pattern
class Program
{
static void Main()
{
ITextEditor editor = new BasicTextEditor(); // Basic editor
editor = new SpellCheckDecorator(editor); // Adding spell check
editor = new GrammarCheckDecorator(editor); // Adding grammar check
editor.Display();
}
}
Output
Imagine a coffee shop where customers can customize their coffee by adding different toppings (like milk, sugar, whipped cream, etc.). Instead of creating separate classes for every possible combination of coffee and toppings, we can use the Decorator pattern to dynamically add these toppings to a base coffee object.
// Step 1: Create a Coffee interface
using System;
public interface ICoffee
{
string GetDescription();
double GetCost();
}
// Step 2: Basic Coffee implementation
public class SimpleCoffee : ICoffee
{
public string GetDescription() => "Simple Coffee";
public double GetCost() => 5.0;
}
// Step 3: Abstract Decorator class
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: Concrete Decorators (Milk, Sugar, Whipped Cream)
public class MilkDecorator : CoffeeDecorator
{
public MilkDecorator(ICoffee coffee) : base(coffee) { }
public override string GetDescription()
{
return _coffee.GetDescription() + ", Milk";
}
public override double GetCost()
{
return _coffee.GetCost() + 1.5;
}
}
public class SugarDecorator : CoffeeDecorator
{
public SugarDecorator(ICoffee coffee) : base(coffee) { }
public override string GetDescription()
{
return _coffee.GetDescription() + ", Sugar";
}
public override double GetCost()
{
return _coffee.GetCost() + 0.5;
}
}
public class WhippedCreamDecorator : CoffeeDecorator
{
public WhippedCreamDecorator(ICoffee coffee) : base(coffee) { }
public override string GetDescription()
{
return _coffee.GetDescription() + ", Whipped Cream";
}
public override double GetCost()
{
return _coffee.GetCost() + 2.0;
}
}
// Step 5: Usage of the Decorator Pattern
class Program
{
static void Main()
{
ICoffee coffee = new SimpleCoffee();
Console.WriteLine($"{coffee.GetDescription()} costs {coffee.GetCost()}");
// Add milk to the coffee
coffee = new MilkDecorator(coffee);
Console.WriteLine($"{coffee.GetDescription()} costs {coffee.GetCost()}");
// Add sugar to the coffee
coffee = new SugarDecorator(coffee);
Console.WriteLine($"{coffee.GetDescription()} costs {coffee.GetCost()}");
// Add whipped cream to the coffee
coffee = new WhippedCreamDecorator(coffee);
Console.WriteLine($"{coffee.GetDescription()} costs {coffee.GetCost()}");
}
}
Output