Design patterns (4 Part Series)
- Design patterns: Singleton
- Design patterns: Factory
- Design patterns: Observer
- 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)
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.
To achieve the decorator pattern, there are four types of classes we need to implement:
Component
ConcreteComponent
that we want to dynamically add to.ConcreteComponent
Decorator
ConcreteComponent
that we’re adding to.Component
's interface (by wrapping the Component
's methods.)ConcreteDecorator
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 interfaceinterface IComponent{void DoSomething();}// 3. The decoratorabstract class Decorator : IComponent{protected IComponent Component { get; set; }public Decorator(IComponent component){this.Component = component;}public void DoSomething(){this.Component.DoSomething();}}// 4. The concrete decoratorclass ConcreteDecorator : Decorator{public ConcreteDecorator(IComponent component) : base(component) {}public void DoSomethingElse(){Console.WriteLine("Doing something else!");}}}
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.