Decorator

Updated: 03 September 2023

“Decorator is a structural design pattern that lets you attach new behaviors to objects by placing these objects inside special wrapper objects that contain the behaviors”

The decorator pattern is about changing the behaviour of a method at runtime instead of compile time. This allows us to modify functionality of a class without modifying the class contents

To do this we wrap the component object in a decorator object

A decorator has the exact same interface/abstract class as the component object (the component) therefore making it exchangable with the component object

“The decorator pattern attaches additional responsibilities to an object dynamically (at runtime). Decorators provide a flexible alternative to subclassing, allows the use of composition to share behaviour”

The decorator inherits from the component and also has a property of type component

The functioning of the decorator pattern makes use of recursion

Example

We could implement a multi-factor authentication type model by using the decorator pattern using something like the following:

Base Classes

Before we can create our concrete implementations we need to establish the base classes for our Logins and Decorators to work from, we can simply create two classes in which our AbstractLogin is the main root, and our LoginDecorator inherits from AbstractLogin

1
// base class for concrete component classes and base decorator
2
public abstract class AbstractLogin
3
{
4
public abstract bool Login();
5
}
6
7
// base class for concrete decorator classes
8
public abstract class LoginDecorator : AbstractLogin
9
{
10
// reference to the inner login that we are decorating
11
// this could be private, public here for the sake of example
12
public abstract AbstractLogin InnerLogin { get; }
13
}

Login Implementation

We can implement our login as any class that inherits from the Login and carries out a login functionality, like so:

1
// implementation of a user type that can login
2
public class LocalUser : AbstractLogin
3
{
4
// handles the login of the local user using a normal process
5
public override bool Login()
6
{
7
System.Console.WriteLine("User Login");
8
return true;
9
}
10
}

Decorator Implementation

Lastly, we need to implement decorators such that each decorator will make some reference/call to the inner AbstractLogin in order to carry out the login behaviour and extend on it’s behaviour

1
// decorates an AbstractLogin
2
// enables a second layer of authentication around the InnerLogin
3
public class EmailPinLoginDecorator : LoginDecorator
4
{
5
// store instance of inner AbstractLogin
6
// to verify before outer login can be done
7
public override AbstractLogin InnerLogin { get; }
8
9
public EmailPinLoginDecorator(AbstractLogin innerLogin)
10
{
11
InnerLogin = innerLogin;
12
}
13
14
15
// handle login by first making call to InnerLogin's process
16
// a decorator should in some way make use of component method as well
17
public override bool Login()
18
{
19
System.Console.WriteLine("Email Pin Verification process");
20
21
var innerLoginResult = InnerLogin.Login();
22
23
if (innerLoginResult)
24
{
25
System.Console.WriteLine("Email's Inner login successful");
26
return true;
27
}
28
else
29
{
30
return false;
31
}
32
}
33
}

Since decorators can be wrapped arbitrarily we can add another decorator which is pretty much exactly the same as above:

1
// basically implemented in the same way as the EmailPinLoginDecorator
2
public class SMSPinLoginDecorator : LoginDecorator
3
{
4
public override AbstractLogin InnerLogin { get; }
5
6
public SMSPinLoginDecorator(AbstractLogin innerLogin)
7
{
8
InnerLogin = innerLogin;
9
}
10
11
public override bool Login()
12
{
13
System.Console.WriteLine("SMS Pin Verification process");
14
15
var innerLoginResult = InnerLogin.Login();
16
17
if (innerLoginResult)
18
{
19
System.Console.WriteLine("Inner login successful");
20
return true;
21
}
22
else
23
{
24
return false;
25
}
26
}
27
}

Using the Decorator Pattern

Given the way we’ve configured our decorator each one takes an instance of AbstractLogin in the constructor, this could be initialized in any method but this one is straightforward to use

We want to create an instance of the LocalUser which has the ability to implement some kind of multi factor auth that needs both a successful email and SMS pin to be verified.

Implementing the decorator pattern with just the EmailPinLoginDecorator would look like so:

1
// create initial user
2
var user = new LocalUser();
3
4
// decorate user with necessary login wrappers
5
var withEmailAuth = new EmailPinLoginDecorator(user);
6
7
// execute the login correctly reaching each level
8
var loginResult = withEmailAuth.Login();

Implementing this using the above classes looks like this:

1
// create initial user
2
var user = new LocalUser();
3
4
// decorate user with necessary login wrappers
5
var withEmailAuth = new EmailPinLoginDecorator(user);
6
var withSMSAndEmailAuth = new SMSPinLoginDecorator(withEmailAuth);
7
8
// execute the login correctly reaching each level
9
var loginResult = withSMSAndEmailAuth.Login();

Based on the way the decorator pattern works we could even make things more complicated by requiring more layers of decorators or can simplify the implementation by removing decorators as you can see above

The way the pattern works means there’s no limit to how many layers you are able to decorate with and allows for very complex implementation and abstraction when using this kind of pattern

References