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 | public interface IProvider |
A basic implementation may be reading data from a socket:
1 | public class DataProvider : IProvider |
A bad implementation of authentication would be to couple the Authentication method
with the DataProvider
itself:
1 | public class Authentication |
The issues with this approach:
- Couples
Authentication
withDataProvider
. - Any new
IProvider
implementation needs to couple once again.
This causes changing ofAuthentication
more troublesome. - Adds another layer of complexity into a single implementation of code.
To battle this coupling a Decorator
approach may help:
1 | public class Authentication |
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 | def LogCall(func): |
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 |
|
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
!
- Legal fees
- Buying the domain
- Marketing everyone that now
Meta
isFacebook
. - Holding the domain for backward compatability and existing businesses and APIs.
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
:
- It inherits
IOperator
. - It contains
IOperator
to decorate the existingTransactionOperator
. - It contains
IMessageQueue
to adapt into it.
A possible implementation would be:
1 | public class KafkaMQOperatorAdapter : IOperator |
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 | public interface IServerDevice |
To transform the ReadingDevice
to cached device we need to do 2 things:
- Implement a
CachedDevice
which holds aReadingDevice
. - Implement a File Reader/Writer or adapts into existing caching mechanisms.
1 | public class CachedDevice : IServerDevice |
If we use Dependency Injection, we may create a CachedDevice
over ReadingDevice
:
1 | public class Bootstrap |
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:
- Be cautious when you introduce the
Adapter
- breaking it is no easy task. - If you need an
Adapter
early in your project - Consider a redesign. Decorator
is good for chaining behaviors.- Abstractions and Decoupling of components allow us to construct system like a children game of building blocks.
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!