Decorator Design Pattern: Comprehensive Explanation

The Decorator pattern is a structural design pattern that enables dynamic addition of responsibilities to an object without altering its underlying structure. It achieves this by wrapping the object with one or more decorator classes that conform to the same interface.


Core Intent

“Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.”

This pattern promotes flexibility and adherence to the Open-Closed Principle by allowing behavior extension through composition rather than inheritance.


When to Use Decorator

ScenarioUse Case
Runtime behavior augmentationAdd features conditionally (e.g., discounts, wrappers)
Avoid subclass proliferationMultiple combinations → exponential classes
Maintain single responsibilityEach decorator adds one concern
Transparent extensionClient unaware of decoration layers
Legacy code enhancementWrap without modification

Avoid when:

  • Overhead from multiple wrappers impacts performance
  • Static extensions suffice → Use inheritance

Where It Is Used in Real Systems

DomainExample
UI ComponentsScrollable + Bordered + Resizable Window
Stream I/OBuffered + Encrypted + Compressed FileStream
E-CommerceProduct with optional add-ons (discount, gift wrap)
LoggingConsole + File + Database Logger
GraphicsShape with patterns, colors, textures

Key Benefits

  • Composability: Stack decorators in any order
  • Single Responsibility: Each decorator focuses on one addition
  • Open/Closed Principle: Extend without modifying core classes
  • Transparency: Decorated objects mimic originals
  • Runtime flexibility: Apply/remove dynamically

Real-World Example: E-Commerce Product Pricing with Add-Ons

An e-commerce platform allows dynamic product customization:

  • Base Product (e.g., Laptop at ₹50,000)
  • Decorators: 10% Discount, Gift Wrapping (+₹500), Insurance (+5% of base)

Decorators stack to compute final price (e.g., Discounted + Wrapped Laptop = ₹44,550), enabling flexible pricing without subclassing every combination.


C# Implementation

using System;

// ==================== COMPONENT ====================

public interface IProduct
{
    string Description { get; }
    decimal CalculatePrice();
}

// ==================== CONCRETE COMPONENT ====================

public class BasicProduct : IProduct
{
    public string Name { get; }
    private readonly decimal _basePrice;

    public BasicProduct(string name, decimal basePrice)
    {
        Name = name;
        _basePrice = basePrice;
    }

    public string Description => Name;

    public decimal CalculatePrice() => _basePrice;
}

// ==================== DECORATOR BASE ====================

public abstract class ProductDecorator : IProduct
{
    protected readonly IProduct _product;

    protected ProductDecorator(IProduct product)
    {
        _product = product ?? throw new ArgumentNullException(nameof(product));
    }

    public virtual string Description => _product.Description;

    public abstract decimal CalculatePrice();
}

// ==================== CONCRETE DECORATORS ====================

public class DiscountDecorator : ProductDecorator
{
    private readonly decimal _discountPercentage;

    public DiscountDecorator(IProduct product, decimal discountPercentage) : base(product)
    {
        _discountPercentage = discountPercentage / 100m;
    }

    public override string Description => $"{_product.Description} (Discounted {(_discountPercentage * 100):F0}%)";

    public override decimal CalculatePrice() => _product.CalculatePrice() * (1 - _discountPercentage);
}

public class GiftWrapDecorator : ProductDecorator
{
    private readonly decimal _wrapFee;

    public GiftWrapDecorator(IProduct product, decimal wrapFee) : base(product)
    {
        _wrapFee = wrapFee;
    }

    public override string Description => $"{_product.Description} + Gift Wrapped";

    public override decimal CalculatePrice() => _product.CalculatePrice() + _wrapFee;
}

public class InsuranceDecorator : ProductDecorator
{
    public InsuranceDecorator(IProduct product) : base(product) { }

    public override string Description => $"{_product.Description} + Insurance";

    public override decimal CalculatePrice() => _product.CalculatePrice() + (_product.CalculatePrice() * 0.05m);
}

// ==================== CLIENT ====================

public class ShoppingCart
{
    public void DisplayItem(IProduct product)
    {
        Console.WriteLine($"{product.Description}: ₹{product.CalculatePrice():F2}");
    }
}

// ==================== DEMO ====================

class Program
{
    static void Main()
    {
        var cart = new ShoppingCart();

        // Base product
        var laptop = new BasicProduct("Laptop", 50000m);
        cart.DisplayItem(laptop);

        // Decorate dynamically
        var discountedLaptop = new DiscountDecorator(laptop, 10m);
        var wrappedDiscountedLaptop = new GiftWrapDecorator(discountedLaptop, 500m);
        var insuredWrappedLaptop = new InsuranceDecorator(wrappedDiscountedLaptop);

        Console.WriteLine("\n=== DECORATOR PATTERN IN E-COMMERCE ===\n");
        cart.DisplayItem(insuredWrappedLaptop);

        // Different combination
        var phone = new BasicProduct("Smartphone", 20000m);
        var insuredPhone = new InsuranceDecorator(new GiftWrapDecorator(phone, 300m));
        cart.DisplayItem(insuredPhone);
    }
}

Sample Output

Laptop: ₹50000.00

=== DECORATOR PATTERN IN E-COMMERCE ===

Laptop (Discounted 10%) + Gift Wrapped + Insurance: ₹55550.00
Smartphone + Gift Wrapped + Insurance: ₹21100.00

UML Class Diagram

Participants in the Decorator Pattern

The classes and objects participating in this pattern include:

  • Component (IProduct)
    • Defines the interface for objects that can be decorated.
  • ConcreteComponent (BasicProduct)
    • The base object receiving additional behaviors.
  • Decorator (ProductDecorator)
    • Implements the Component interface.
    • Holds a reference to a Component and delegates calls.
  • ConcreteDecorator (DiscountDecorator, GiftWrapDecorator, InsuranceDecorator)
    • Adds specific behaviors (e.g., modifies price).
    • Wraps the component and extends functionality.
  • Client
    • Composes decorators around the component dynamically.

Summary Table

ParticipantRole in E-Commerce Example
ComponentIProduct – pricing interface
ConcreteComponentBasicProduct – base item
DecoratorProductDecorator – base wrapper
ConcreteDecoratorDiscountDecorator, GiftWrapDecorator, InsuranceDecorator
ClientShoppingCart – displays final priced item

Advantages in E-Commerce

BenefitImpact
Flexible pricingStack discounts, add-ons dynamically
No explosionAvoid subclasses for every combo
ModularReuse decorators across products
RuntimeApply based on user selection

Decorator vs Composite

AspectDecoratorComposite
StructureLinear chain (wrapper)Tree hierarchy (part-whole)
PurposeAdd behaviorsGroup objects
ChildrenSingle wrapped objectMultiple children
ExampleProduct + Discount + WrapCategory + Products

Conclusion

The Decorator pattern is highly suitable for e-commerce when extending product functionality (e.g., pricing add-ons) dynamically. It ensures modularity and scalability, allowing seamless feature stacking without rigid inheritance hierarchies.

Widely applied in .NET Streams (BufferedStream), Java I/O, and e-commerce engines like WooCommerce for modular extensions.

Uma Mahesh
Uma Mahesh

Author is working as an Architect in a reputed software company. He is having nearly 21+ Years of experience in web development using Microsoft Technologies.

Articles: 283