Back to Structural: Adapter, Decorator, Proxy, Facade
Q6Question 6 of 6

Design using the Decorator pattern

Key Concepts
  • The Decorator pattern wraps an object to add behaviour dynamically, stacking features without an explosion of subclasses.
  • Both the base component and decorators implement the same interface, so a decorated object is used anywhere the original was expected.
  • Each decorator holds a reference to a component, delegates the core call, and adds its own contribution before or after.
  • The strength over subclassing is combinatorial: n features give all 2^n combinations with just n decorator classes.
  • Adding a new feature is a new decorator class, satisfying Open/Closed and Single Responsibility principles.
  • Ordering matters when one decorator's output feeds another; keep decorators commutative where possible.

Definition

The Decorator pattern wraps an object to add behaviour dynamically, stacking features without an explosion of subclasses.
  • Decorator – attaches additional responsibilities to an object dynamically by wrapping it in a decorator that implements the same interface and delegates to the wrapped object.
  • Component interface – the shared type that both the base object and all decorators implement.
  • Concrete component – the base object, for example a BasicAccount.
  • Concrete decorators – wrappers like OverdraftFeature, RewardsFeature, and InsuranceFeature, each adding one concern.
  • Composition over inheritance – stacking decorators at runtime avoids the subclass explosion of combining features with inheritance.

Think of adding optional features onto a base bank account at National Bank of India. Rajesh starts with a basic account; he wraps it with overdraft protection, then wraps that with premium rewards. Each wrapper adds its own fee or benefit on top of whatever it wraps, so you can stack features in any combination without creating a separate class for every possible mix.

Understanding the Concepts

What is the Decorator pattern?

The Decorator pattern attaches additional responsibilities to an object dynamically by wrapping it in a decorator that implements the same interface. The decorator delegates the core call to the wrapped object and adds its own contribution before or after.

JAVA
package com.bank.decorator;

import java.math.BigDecimal;

public interface AccountFeature {
    BigDecimal monthlyFee();
    String describe();
}

Why does the Decorator pattern exist?

Without decorators, combining optional features requires a subclass for every combination. With three features (overdraft, rewards, insurance) you would need:

  • BasicAccount
  • OverdraftAccount
  • RewardsAccount
  • InsuranceAccount
  • OverdraftRewardsAccount
  • OverdraftInsuranceAccount
  • RewardsInsuranceAccount
  • OverdraftRewardsInsuranceAccount

That is 2^3 = 8 subclasses, and every new feature doubles the count. Decorators collapse this to one class per feature.

JAVA
// Without decorators: 8 classes for 3 features (bad)
class OverdraftRewardsInsuranceAccount extends BasicAccount { }

// With decorators: 3 classes, composed freely (good)
new RewardsFeature(new OverdraftFeature(new BasicAccount()))

How do you implement the base component?

The base component implements the interface and provides the default behaviour.

JAVA
package com.bank.decorator;

import java.math.BigDecimal;

public class BasicAccount implements AccountFeature {
    @Override
    public BigDecimal monthlyFee() {
        return new BigDecimal("100.00"); // base maintenance fee
    }

    @Override
    public String describe() {
        return "Basic Account";
    }
}

How do you implement decorators?

Each decorator implements the same interface, holds a reference to the wrapped component, and adds its own contribution.

JAVA
package com.bank.decorator;

import java.math.BigDecimal;

public class OverdraftFeature implements AccountFeature {
    private final AccountFeature wrapped;

    public OverdraftFeature(AccountFeature wrapped) {
        this.wrapped = wrapped;
    }

    @Override
    public BigDecimal monthlyFee() {
        return wrapped.monthlyFee().add(new BigDecimal("50.00"));
    }

    @Override
    public String describe() {
        return wrapped.describe() + " + Overdraft Protection";
    }
}

public class RewardsFeature implements AccountFeature {
    private final AccountFeature wrapped;

    public RewardsFeature(AccountFeature wrapped) {
        this.wrapped = wrapped;
    }

    @Override
    public BigDecimal monthlyFee() {
        return wrapped.monthlyFee().add(new BigDecimal("75.00"));
    }

    @Override
    public String describe() {
        return wrapped.describe() + " + Premium Rewards";
    }
}

public class InsuranceFeature implements AccountFeature {
    private final AccountFeature wrapped;

    public InsuranceFeature(AccountFeature wrapped) {
        this.wrapped = wrapped;
    }

    @Override
    public BigDecimal monthlyFee() {
        return wrapped.monthlyFee().add(new BigDecimal("200.00"));
    }

    @Override
    public String describe() {
        return wrapped.describe() + " + Life Insurance";
    }
}

How do you compose decorators at runtime?

Stack decorators in any combination to match what the customer wants.

JAVA
package com.bank.demo;

import com.bank.decorator.*;
import java.math.BigDecimal;

public class DecoratorDemo {
    public static void main(String[] args) {
        // Rajesh: basic + overdraft + rewards
        AccountFeature rajesh = new RewardsFeature(
            new OverdraftFeature(new BasicAccount()));
        System.out.println(rajesh.describe());
        System.out.println("Monthly fee: INR " + rajesh.monthlyFee());

        // Priya: basic + insurance
        AccountFeature priya = new InsuranceFeature(new BasicAccount());
        System.out.println(priya.describe());
        System.out.println("Monthly fee: INR " + priya.monthlyFee());
    }
}
TEXT
Basic Account + Overdraft Protection + Premium Rewards
Monthly fee: INR 225.00
Basic Account + Life Insurance
Monthly fee: INR 300.00

How is Decorator different from inheritance?

| Aspect | Decorator | Inheritance |
|---|---|---|
| Binding | Runtime composition | Compile-time hierarchy |
| Combinations | Stack any subset freely | One subclass per combination |
| Growth | n features = n classes | n features = 2^n classes |
| Adding a feature | New decorator class, zero changes | Edit or extend many subclasses |
| Flexibility | Order and selection at runtime | Fixed at compile time |

What are common mistakes?

The first mistake is making the decorator depend on the concrete component instead of the interface. This prevents stacking.

JAVA
// Wrong: depends on BasicAccount, cannot wrap other decorators
public class OverdraftFeature {
    private final BasicAccount account; // too specific
}

// Correct: depends on the interface, can wrap anything
public class OverdraftFeature implements AccountFeature {
    private final AccountFeature wrapped; // abstract
}

The second mistake is overly deep stacks that hurt readability. Keep the decorator count reasonable and consider a builder when the configuration becomes complex.

When should you use the Decorator pattern?

Use it when a base object has many optional features that customers want to combine freely, when subclassing would explode, or when you need to add behaviour without modifying existing classes.

Do not use it when there is only one fixed configuration, or when the overhead of wrapper objects matters in a performance-critical path.

JAVA
// Good use case: optional account add-ons
AccountFeature acc = new RewardsFeature(
    new OverdraftFeature(new BasicAccount()));

// Bad use case: only one configuration exists
AccountFeature acc = new BasicAccount(); // no decorators needed

What should a senior engineer know?

Ordering matters when one decorator's output feeds another. In the account example, fees are additive so order does not matter. But in a notification system where encryption runs before logging, the order determines whether the log shows encrypted or plain text. Keep decorators commutative where possible and document the order where it is significant.

At National Bank of India, Pankaj models optional account add-ons like overdraft, rewards, and insurance as decorators, so Priya can mix and match features and the monthly fee composes automatically, without the bank ever maintaining a class for every combination.

JAVA
// Ordering example: encryption before vs after logging
Notifier v1 = new LoggingDecorator(new EncryptionDecorator(sms));
// Logs: plain text, sends: encrypted

Notifier v2 = new EncryptionDecorator(new LoggingDecorator(sms));
// Logs: encrypted text, sends: encrypted

Visual Overview

DIAGRAM
  Decorator Chain for Rajesh's Account
  ────────────────────────────────────
  RewardsFeature        fee: +75.00
       │
       ▼
  OverdraftFeature      fee: +50.00
       │
       ▼
  BasicAccount          fee: 100.00
  ─────────────────────────────────
  Total monthly fee:    INR 225.00
DIAGRAM
  Subclass explosion vs Decorator
  ──────────────────────────────
  3 features:
    Inheritance: 2^3 = 8 subclasses
    Decorator:   3 classes, any combination

  4 features:
    Inheritance: 2^4 = 16 subclasses
    Decorator:   4 classes, any combination

Key Takeaway

The Decorator pattern attaches extra responsibilities to an object dynamically by wrapping it in a decorator that implements the same interface, delegates to the wrapped object, and adds its own behaviour. It is the flexible alternative to subclassing, and Rajesh reaches for it whenever a base object has many optional features that customers want to combine freely.

The structure is a shared interface implemented by both the base component and the decorators. Each decorator holds a reference to a component, delegates the core call, and contributes its own part. Because the decorator implements the same interface, a decorated object can be used anywhere the original was expected, so the caller never needs to know how deep the stack goes.

The decisive advantage over subclassing is combinatorial. If a product has several optional features, subclassing would require a separate class for every possible combination, which explodes quickly, whereas decorators let you compose any subset at runtime by stacking wrappers. This satisfies the Open/Closed principle, because a new feature is a new decorator, and the Single Responsibility principle, because each decorator does exactly one thing.

The senior nuance to keep in mind is ordering. Because each decorator wraps the same interface, callers stay unaware of the stack, but the order can matter when one decorator's output feeds the next. At National Bank of India, Pankaj models optional account add-ons like overdraft, rewards, and insurance as decorators, so a customer can mix and match features and the monthly fee composes automatically, without the bank ever maintaining a class for every combination.

Remember one sentence: the Decorator wraps an object to add behaviour dynamically, stacking features at runtime and avoiding the subclass explosion of combining features with inheritance.

---

Share
Use ← → arrow keys to navigate