Practical Patterns: Refactoring with Decorator Design Pattern

This post is the second in the series of Design Patterns. You may refer to the earlier article here Practical Patterns: Refactoring with Bridge Design Pattern

One cannot always start by applying design patterns with a new or greenfield project. Majority of the times we are working on existing or brownfield projects. We should be able to identify how the requirement can be retrofit into an existing code while retaining/enhancing maintainability and not adding any technical debts.
Earlier we had observed how through Bridge design pattern we were able to structurally separate the classes so they could be extended independently. Bridge pattern is applied at the class level.
Decorator design pattern is applied to an object and not to a class. Once again this is done through composition rather than inheritance.

Let us continue with the code from the previous article in seeing how we can manage new requirements through Decorator pattern.
We’ll naturally come across a few new requirements which I am going to conjure up. Firstly, the business requires each transaction to get audited. They also require notification to be sent for certain transactions. If we look at the requirements, we need to add behaviour to the existing implementations. They need to happen when a transaction is taking place.

Any class which needs to provide a transaction needs to implement ITransaction interface. This is implemented by Personal and Corporate classes. The auditing needs to be done when there is a deposit or withdrawal. We could introduce a base class which implements auditing. This class can be inherited by those who implement ITransaction. But, we would be exhausting the only inheritance allowed on these classes since the language supports only single inheritance. Even if we go ahead with this option, it would affect all the instances of the original class. What about the other requirement? How can we apply notifications to be sent from only certain instances? If and when we decide to remove notifications, how do we efficiently do it? We cannot selectively alter the behaviour of the objects during runtime through sub-classing. Also handling both the requirements in a base class would violate the Single Responsibility Principle(SRP).

Decorator pattern helps you in applying SRP while also selectively allowing us to add behaviour to objects and in any combination.
As many examples showcase, it is not always required or necessary to have an abstract class to implement decorator. But in many cases they might be convenient to have and we’ll see why. But for now we can do without one and have just the concrete decorators. All we need is to implement the interface which we wish to extend and also have a reference of the interfaces object internally. The decorators which we implement can be stacked in any order to add behaviour and repeated too.Decorator Pattern

The new changes are highlighted in blue.

AuditTransactionDecorator implements the first requirement of audting.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class AuditTransactionDecorator : ITransaction
{
private readonly ITransaction transaction;
public AuditTransactionDecorator(ITransaction transaction)
{
this.transaction = transaction;
}

public void Deposit(decimal amount)
{
Console.WriteLine("Auditing amount BEFORE deposit transaction {0}", amount);
this.transaction.Deposit(amount);
Console.WriteLine("Auditing amount AFTER deposit transaction {0}", amount);
}

public decimal GetBalance()
{
return this.transaction.GetBalance();
}

public void Withdraw(decimal amount)
{
Console.WriteLine("Auditing amount BEFORE withdraw transaction {0}", amount);
this.transaction.Withdraw(amount);
Console.WriteLine("Auditing amount AFTER withdraw transaction {0}", amount);
}
}

NotifyTransactionDecorator implements the next requirement of notifying.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class NotifyTransactionDecorator : ITransaction
{
private readonly ITransaction transaction;
public NotifyTransactionDecorator(ITransaction transaction)
{
this.transaction = transaction;
}

public void Deposit(decimal amount)
{
this.transaction.Deposit(amount);
}

public decimal GetBalance()
{
return this.transaction.GetBalance();
}

public void Withdraw(decimal amount)
{
this.transaction.Withdraw(amount);
if (amount > 100)
{
Console.WriteLine("NOTIFY: The amount withdrawn has exceeded $100. The amount was {0}",
amount);
}
}
}

As you may notice, we are able to implement the new requirements in isolation and not having to modify the existing code.
In our AccountBase where we are creating the instance of type ITransaction, we can now decorate them with the new objects.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
abstract class AccountBase : IAccount
{
protected ITransaction transactionImp;
public AccountBase(string name, AccountType type)
{
Name = name;
switch (type)
{
case AccountType.Personal:
transactionImp = new AuditTransactionDecorator(
new Personal(1000));
break;
case AccountType.Corporate:
transactionImp = new NotifyTransactionDecorator(
new Corporate(5000));
break;
default:
throw new NotImplementedException("Unknown account type");
}
}

public abstract void Deposit(decimal amount);
public abstract void Withdraw(decimal amount);
public abstract decimal Balance { get; }
public string Name { get; private set; }
}

In line 10 and 14 we inject the original instance into the decorators. The decorators would now call the injected instances method along with its own. We could also stack the decorators together.

1
2
3
transactionImp = new AuditTransactionDecorator(
new NotifyTransactionDecorator(
new Corporate(5000)));

So when do we need an abstract class for decorators? Let’s take a look at our requirements again. For auditing requirement, we needed to decorate only the Deposit and Withdraw methods. For GetBalance we just had to forward the call to the internal reference of the interface’s concrete instance. For the notification, we just needed to add behaviour to the Withdraw method. Each requirement had a different set of methods being decorated. If we had to create multiple decorator classes targeting same methods, then a base class would have helped since it would provide a default implementation to methods which need to only forward the calls and allow us to implement only the targeted methods. This would also serve as a document indicating that classes derived from this base are decorators.
For example:

1
2
3
4
5
6
7
8
9
10
11
12
abstract class TransactionDecoratorBase : ITransaction
{
internal ITransaction transaction;

public abstract void Deposit(decimal amount);
public abstract void Withdraw(decimal amount);

public decimal GetBalance()
{
return transaction.GetBalance();
}
}

The implementation of auditing would look like this-

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class AuditTransactionDecorator : TransactionDecoratorBase
{
public AuditTransactionDecorator(ITransaction transaction)
{
this.transaction = transaction;
}
public override void Deposit(decimal amount)
{
Console.WriteLine("Auditing amount BEFORE deposit transaction {0}", amount);
this.transaction.Deposit(amount);
Console.WriteLine("Auditing amount AFTER deposit transaction {0}", amount);
}
public override void Withdraw(decimal amount)
{
Console.WriteLine("Auditing amount BEFORE withdraw transaction {0}", amount);
this.transaction.Withdraw(amount);
Console.WriteLine("Auditing amount AFTER withdraw transaction {0}", amount);
}
}

Which takes us to the classic textbook diagram.Decorator pattern classic example