May 6, 2022

Design pattern highlights - Decorator & Adapter

Design Pattern highlights - Decorator and Adapter

Both patterns of the same nature - it uses abstraction and composition to construct behavior.
The decorator adds new behavior.
The Adapter changes the behavior to accommodate limitations of existing interfaces.

These 2 patterns are excellent on reusing code, but their purpose is different.
Let’s have a look:

Highlight - The Decorator Pattern

A Design pattern that adds functionality without the need to alter the existing behavior.

The decorator design pattern is a behavioral pattern.

It’s constructed from the same behavior declaration but adds functionality that isn’t necessarily related to the same behavior.

It gets a lower score of coupling since it doesn’t have to be related to the same behavior.
This allows programmers to alter this to variety of needs specially classes and services that couple certain behaviors.

Diagram and Usage

IProvider allocates data through the Get() method.
DataProvider is the implementation of the IProvider.

To accomodate security needs we need to add a way to authenticate the Get() request of the DataProvider.
Since adding internally authentication in the DataProvider couples the internal behavior it is not wise to couple DataProvider and the Authentication method.

Instead we can allocate a new IProvider called AuthenticateProvider which will couple the Authentication method with the IProvider abstraction instead!

Example 1 - Authentication as decorator

The provider receives data through the sync Get method.

1
2
3
4
public interface IProvider
{
byte[] Get();
}

A basic implementation may be reading data from a socket:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class DataProvider : IProvider
{
private Socket mSocket;

public byte[] Get()
{
if(mSocket.IsReady())
{
return mSocket.ReadBytes();
}
else
{
return new byte[0];
}
}
}

A bad implementation of authentication would be to couple the Authentication method with the DataProvider itself:

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
public class Authentication
{
public static bool Authenticate()
{
/*Some impl... */
return true;
}
}

public class DataProvider : IProvider
{
private Socket mSocket;

public byte[] Get()
{
if(Authentication.Authenticate() && mSocket.IsReady())
{
return mSocket.ReadBytes();
}
else
{
return new byte[0];
}
}
}

The issues with this approach:

To battle this coupling a Decorator approach may help:

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
public class Authentication
{
public static bool Authenticate()
{
/*Some impl... */
return true;
}
}

public class DataProvider : IProvider { /* ... */ }

public class AuthenticateProvider : IProvider
{
private IProvider mProvider;

public byte[] Get()
{
if(Authentication.Authenticate())
{
return mProvider.Provide();
}

throw new NotAuthenticatedException();
}
}

This approach supports Single Responsibility because DataProvider isn’t responsible for calling authentication anymore.

Example 2 - Python decorators

Python allows decorates as part of the language.
This is supreme - adding new functionality and altering functionality without the need to change the interfaces.

This is possible because of the dynamic nature of Python.

Func calls a method that it was assigned to:

To alter or add a new behavior we assign a “Function in the middle” that does new behavior,
Then we call the old behavior as expected:

In python

A basic method of decorating a function would be to add an internal method that alters the flow of the execution:

1
2
3
4
5
6
7
8
9
10
11
def LogCall(func):
def InternalLog():
print(f"Calling Func {SayHi.__name__}")
func()
return InternalLog

def SayHi():
print("Hello World")
SayHi = LogCall(SayHi)

SayHi()

But then to alter the SayHi we need to directly call the LogCall method.
Python allows us to make it easier by adding @<function> over method:

1
2
3
@LogCall
def SayHi():
print("Hello World")

This adds the LogCall behavior over SayHi.

The Decorator pattern is implemented in python- many features are easily built this way.
It allows interesting alteration of behavior flow without the need to explicitly change interfaces.


Highlight - The Adapter Pattern

A Design pattern that enables changing behavior to suit a particular interface.

The Adapter excells at reuseability.
However it never should be used in a new design - only adaptivity in existing code.
If you use it in a new system it means your design is flawed already!.

Of course you need to use your reason - The Decorator is different than the Adapter.

Adapting behavior

It may cost you a fortune to change a single interface - for example, imagine what facebook needs to pay to change their main entry point from www.facebook.com to www.meta.com!

Electricity Adapters

Another great example are electrical adapters.
Many countries use different kind of sockets - this is because the electrical systems are governed by different entities.

As a tourist in a foreign country - What do you need to charge your phone or a comunpter?
That’s right, - An Adapter!

If you have a Type A plug and a Type D socket, you’ll need the proper adapter:

Diagram and Usage

Imagine we are a system providing transactions for banks.
The current system works with TransactionOperator which processes transactions through the Process() method.

We want to add to this existing system a Kafka MQ.

Kafka is an event streaming platform which enables safe passing of data events.

The TransactionOperator isn’t reliable as a Kafa MQ, if the system goes down we may lose information.
For banking - this is cruical not to lose transactions.

For this purpose we introduce a new component called KafkaMQOperatorAdapter:

A possible implementation would be:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class KafkaMQOperatorAdapter : IOperator
{
private IOperator mOriginalOperator; // Original socket
private IMessageQueue mQueue; // Adapter

public void Process(Transaction transaction)
{
var messageObj = Create(transaction);

mQueue.Insert(messageObj);

mOriginalOperator.Process(transaction);
}

private MessageUpdate Create(Transaction transaction) { /* ... */}
}

Example - Cache adapter

We’re running a server for a game and we want to adapt the existing reading mechanism into cached reading.
So of course we could edit the current reading device to use a cached folder.
How do we do that without changing the existing implementation?
An Adapter!

The server device reads

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public interface IServerDevice
{
MessagePacket Read(MessageType type, GUID id);
}

public class ReadingDevice : IServerDevice
{
private TcpClient mClient;

MessagePacket Read(MessageType type, GUID id)
{
if(mClient.AvaiableBytes <= 0)
{
return new byte[0];
}

// Read according to type

return new MessagePacket(/* ... Info ... */);
}
}

To transform the ReadingDevice to cached device we need to do 2 things:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class CachedDevice : IServerDevice
{
private IServerDevice mOriginalDevice;
private ICache mCache;

CachedDevice(IServerDevice device, ICache cache)
{
mCache = cahce;
mOriginalDevice = device;
}

MessagePacket Read(MessageType type, GUID id)
{
if(mCache.IsCached(id))
{
return mCache.Get(id).To<MessagePackage>();
}
var message = mOriginalDevice.Read(type,id);

mCache.Cache(id, message);

return message;
}
}

If we use Dependency Injection, we may create a CachedDevice over ReadingDevice:

1
2
3
4
5
6
7
8
public class Bootstrap
{
public void Register(IContainer container)
{
var original = new ReadingDevice();
container.Register<IServerDevice,CacheDevice>(original);
}
}

The Decorator and Adapter design patterns are basic building blocks which allow us to add functionality or change it.

I used these patterns over and over in my different projects.
The Adapter pattern is a good pattern for legacy code because it allows us to implement new features over older code.
It’s not perfect but it works!

The Decorator in the other hand is excellent for chaining and introducing new behavior without the need to introduce new abstractions.
It decouples components and allows Single Responsibility to thrive.

Few pointers:


Want to share your opinion?
Do you have feedback?
Any question that is not answered?

Join our discord!

The home to simpletons like us who just love to code


Thanks for reading!

על הפוסט

הפוסט נכתב על ידי Ilya, רישיון על ידי CC BY-NC-ND 4.0.

שתפו את הפוסט

Email Facebook Linkedin Print

קנו לי קפה

#Software#DesignPatterns