Trusted answers to developer questions

Design patterns: Decorator

Get the Learn to Code Starter Pack

Break into tech with the logic & computer science skills you’d learn in a bootcamp or university — at a fraction of the cost. Educative's hand-on curriculum is perfect for new learners hoping to launch a career.

Design patterns (4 Part Series)

  1. Design patterns: Singleton
  2. Design patterns: Factory
  3. Design patterns: Observer
  4. Design patterns: Decorator

Hey there, my last posts looked at creational and behavioral design patterns. This time we’re going to be going over our first structural pattern, the decorator pattern.

Structural patterns give us a way of combining objects into larger structures while keeping these structures flexible and efficient. When you think of the decorator pattern, think of Matryoshka dolls (the dolls that stack inside of one another). You’ll soon see that the decorator pattern gives our code flexibility at run-time while extending class functionality.

If you don’t know what a wrapper class is, you might want to check out this post first! (But it’s not required)

Overview

  • Definition
  • Implementation
  • Example

Definition

Decorate: make (something) look more attractive by adding extra items or images to it.

The decorator pattern allows us to add functionality to our classes without directly modifying or inheriting from them. We’re able to add behavior to our classes by wrapping them up inside other classes.

We can dynamically add responsibilities to objects with a component and decorator relationship. The decorator conforms to the component’s interface while also having a component object within itself. The decorator object is then effectively able to add functionality to the component by adding functionality to itself.

Implementation

To achieve the decorator pattern, there are four types of classes we need to implement:

  1. Component
    • Defines the interface for the ConcreteComponent that we want to dynamically add to.
  2. ConcreteComponent
    • The calls we are adding functionality to.
  3. Decorator
    • Maintains a reference to the ConcreteComponent that we’re adding to.
    • Implements Component's interface (by wrapping the Component's methods.)
  4. ConcreteDecorator
    • Adds the functionality.

Using C#, the pattern looks like this:

using System;
using System.Collections.Generic;
public class Program
{
public static void Main()
{
ConcreteComponent component = new ConcreteComponent();
component.DoSomething();
ConcreteDecorator decorator = new ConcreteDecorator(new ConcreteComponent());
decorator.DoSomethingElse();
}
// 2. The concrete component (what we're decorating)
class ConcreteComponent : IComponent
{
public void DoSomething()
{
Console.WriteLine("Doing something!");
}
}
// 1. The component's interface
interface IComponent
{
void DoSomething();
}
// 3. The decorator
abstract class Decorator : IComponent
{
protected IComponent Component { get; set; }
public Decorator(IComponent component)
{
this.Component = component;
}
public void DoSomething()
{
this.Component.DoSomething();
}
}
// 4. The concrete decorator
class ConcreteDecorator : Decorator
{
public ConcreteDecorator(IComponent component) : base(component) {}
public void DoSomethingElse()
{
Console.WriteLine("Doing something else!");
}
}
}

Example

Decorators are good to use when you don’t want to/can’t change the behavior of a class. You can run into this situation for a lot of different reasons. For example, you might be working with legacy code that shouldn’t be modified, or working with a library that you cannot modify.

Let’s write a program that adds functionality to a Calculator class. Our Calculator class only has an Add method right now, but we want to add a Subtract method so we’ll create a decorator for the class.

using System;
public class Program
{
public static void Main()
{
Calculator calculator = new Calculator();
Console.WriteLine("Regular Calculator:");
Console.WriteLine(calculator.Add(4, 3) + "\n");
BetterCalculator betterCalculator = new BetterCalculator(calculator);
Console.WriteLine("Decorated Calculator:");
Console.WriteLine(betterCalculator.Add(4, 3));
Console.WriteLine("But I can also subtract!");
Console.WriteLine(betterCalculator.Subtract(4, 3));
}
class Calculator : ICalculator
{
public override int Add(int x, int y)
{
return x + y;
}
}
abstract class ICalculator
{
public abstract int Add(int x, int y);
}
class Decorator : ICalculator
{
ICalculator myCalc { get; set; }
public Decorator(ICalculator calculator)
{
this.myCalc = calculator;
}
public override int Add(int x, int y)
{
return this.myCalc.Add(x, y);
}
}
class BetterCalculator : Decorator
{
public BetterCalculator(ICalculator calc) : base(calc) {}
public int Subtract(int x, int y)
{
return x - y;
}
}
}

Disclaimer: There are a lot of resources for learning design patterns, and they can be implemented in different ways. I would recommend exploring more resources when finished with this post.

RELATED TAGS

design

CONTRIBUTOR

Ryan Henness
Attributions:
  1. undefined by undefined
Did you find this helpful?