Introduction
My name is Radoslaw Sadowski and I'm a Microsoft Certified Software Developer. Since the beginning of my career, I have been working with Microsoft technologies.
I wrote 2 articles on CodeProject:
in which I showed some useful programming techniques that I've learned during my career.
I recommend reading them too, but this one is completely independent.
In this article, I want to show how the Adapter Design Pattern can make your life easier while developing Enterprise Software.
In this article, I would like to show you 4 common software development problems and suggest solutions for them. I will use an Adapter Design Pattern as a problem solver!
The Goal of the Article
The goal of this article is not to explain how the Adapter Pattern works. There are many great articles with explanation of that. My goal is to show how it can help you in your everyday programming at work.
I will show extremely practical examples. I came across these kind of problems during my work as a Software Developer so there is a big chance that you will experience it as well or you already did.
What You Have to Know to Understand this Article
To understand this article, you have to:
- have an understanding of the Adapter Pattern at minimum in theory
- have an understanding of Dependency Injection Design Pattern
- have a basic understanding of automated testing
- have a basic understanding of mocking objects for unit testing
- be familiar with C# language
Stop talking, start doing!
Brief Reminder of the Adapter Design Pattern
As I mentioned before, I will not teach what the Adapter Pattern is, I just want to remind you briefly a general idea of it, so…
Adapter pattern is one of the Structural Design Patterns.
Wikipedia says:
An adapter helps two incompatible interfaces to work together.
Wikipedia says:
Interfaces may be incompatible but the inner functionality should suit the need.
Wikipedia says:
The Adapter design pattern allows otherwise incompatible classes to work together by converting the interface of one class into an interface expected by the clients.
And that is exactly what it does.
In the above picture, we can see the class diagram of the Adapter pattern. We basically have there:
Client
– A class in our application, the caller
Adaptee
– A class which we want to use from our Client
, but we cannot because of incompatible interface
Adapter
– A class which allows us to use the Adaptee
class from the Client
So to sum up, if we want to use an external component from our system, there is no interface via which we can do that, we can use the Adapter Pattern.
Let's go to concretes!
First Example: Static .NET Classes
Imagine now that you are developing a green field project and you have a class in your system which is responsible for storing files on the local disk.
The method responsible for this action is getting the file as a byte array and writing it to a local disk using File
class from the System.IO
namespace.
Your code is very clean, you are using Dependency Injection to inject configuration and the logger and your code looks as below:
public class FileSystemManager
{
private readonly IConfiguration _configuration;
private readonly ILogger _logger;
public FileSystemManager(IConfiguration configuration, ILogger logger)
{
_configuration = configuration;
_logger = logger;
}
public bool SaveFile(FileRepresentation file)
{
try
{
var path = string.Format("{0}{1}", _configuration.GetPathToRepository(file.Repository),
_fileHelper.GetFileNameInRepository(file.Name));
File.WriteAllBytes(path, file.Content);
return true;
}
catch (Exception ex)
{
_logger.LogError();
return false;
}
}
}
You construct the path to store the file and then save the file using WriteAllBytes
method.
So far so good! But... we are not able to hide a static
File
class behind the interface because:
- it's a
static
class and cannot implement any interface
- it's a built in .NET Framework class and we cannot make it implement our custom interface
Is that an issue? Yes it is!
Imagine that you want to write unit tests for this class, to check:
- if function
SaveFile
returns true
value if the file was stored correctly
- if function
SaveFile
returns false
value if the file was NOT stored correctly
- if
WriteAllBytes
method was called with a properly constructed path
- what will be an output if
WriteAllBytes
method will throw an error
- and so on..
How can we create a UNIT TEST (note that I'm not talking about the integration test now) if we can't mock the File
class??
This is our first problem!
What can we do now then???
Use our friend – the Adapter Design Pattern!
But how can we do it?
Let's introduce an interface first:
public interface IFileService
{
void SaveFile(string path, byte[] content);
}
and change our FileSystemManager
to UnitTestableFileSystemManager
:
public class UnitTestableFileSystemManager
{
private readonly IConfiguration _configuration;
private readonly ILogger _logger;
private readonly IFileService _fileService;
public UnitTestableFileSystemManager(IConfiguration configuration,
ILogger logger, IFileService fileService)
{
_configuration = configuration;
_logger = logger;
_fileService = fileService;
}
public bool SaveFile(FileRepresentation file)
{
try
{
var path = string.Format("{0}{1}", _configuration.GetPathToRepository(file.Repository),
_fileHelper.GetFileNameInRepository(file.Name));
_fileService.SaveFile(path, file.Content);
return true;
}
catch (Exception ex)
{
_logger.LogError();
return false;
}
}
}
In the code above, I replaced a File
class with an IFileService
interface and made this class allow to inject into it a concrete implementation of this interface.
Now we can easily mock up our file service and inject it into the UnitTestableFileSystemManager
class. As we can treat the configuration class and the logger class in the same way, we are now able to write as many unit tests as we want!
But wait a second, what implementation will we inject into the UnitTestableFileSystemManager
class while the static File
class does not implement an IFileService
interface?
Now we need the Adapter
:
public class FileServiceAdapter : IFileService
{
public void SaveFile(string path, byte[] content)
{
File.WriteAllBytes(path, content);
}
}
You can notice that FileServiceAdapter
class uses File
class internally. The goal has been achieved! Hurray!!!
You can use the same approach when you will have the same problem with other classes from .NET Framework like SmtpClient
or any other class from 3rd party libraries.
We gained even more than only the ability for unit testing, we are now able to replace the FileServiceAdapter
with any other implementation.
Second Example: Replace Custom Logger with Third Party Logger
Now imagine that you are developing implemented a few years ago system. Unfortunately it is happening to us very often :) Too often...
System is using a DatabaseLogger
class whenever there is a need to log that some event had happened or to log an exception. The good thing is that it was implemented by a good developer, and the DatabaseLogger
implementation is hidden behind the ILogger
interface:
public interface ILogger
{
void LogError(Exception ex);
void LogInfo(string message);
}
so the code of the particular classes in the application looks like below:
public class SampleClassOne
{
private readonly ILogger _logger;
public SampleClassOne(ILogger logger)
{
_logger = logger;
}
public void SampleMethod()
{
_logger.LogInfo("User was added!");
_logger.LogInfo("Email was sent!");
}
}
public class SampleClassTwo
{
private readonly ILogger _logger;
public SampleClassTwo(ILogger logger)
{
_logger = logger;
}
public void SampleMethod()
{
try
{
_logger.LogInfo("File was saved!");
}
catch (Exception ex)
{
_logger.LogError(ex);
}
}
}
and this is the implementation of the DatabaseLogger
class:
public class DatabaseLogger : ILogger
{
public void LogError(Exception ex)
{
}
public void LogInfo(string message)
{
}
}
Our task is to replace the DatabaseLogger
with a Log4Net (external library which implements logging functionality) logger, because the technical leader decided that we are standardising our systems and we will use the Log4Net library for all systems in the company.
No problem we think! If the implementation is hidden behind the interface, we will just replace the DatabaseLogger
class with a class that contains the logger implementation from the Log4Net library. But then, we realize that the logger implementation from Log4Net does not implement our ILogger
interface, it implements ILog
interface which is incompatible with the interface of our system.
So we have two incompatible interfaces, the classic problem to solve by the Adapter Design Pattern!
So let's solve it!
All we have to do is to create Log4NetAdapter
class which will implement the ILogger
interface:
public class Log4NetAdapter : ILogger
{
private readonly ILog _logger;
public Log4NetAdapter()
{
_logger = LogManager.GetLogger(typeof(Log4NetAdapter));
}
public void LogError(Exception ex)
{
_logger.Error(ex);
}
public void LogInfo(string message)
{
_logger.Info(message);
}
}
And tell the application to use the Log4NetAdapter
class instead of the DatabaseLogger
class as the implementation of the ILogger
interface in the dependency injection configuration.
You can notice that our Log4NetAdapter
class is using Log4Net logger internally:
_logger = LogManager.GetLogger(typeof(Log4NetAdapter));
Of course, to make Log4Net work, you have to add a configuration of a logger in code or in the configuration file and register Log4Net when the application starts but it is not related to the topic of this article.
Third Example: Replace Custom Logger with Different Custom Logger
Yes, the title of this chapter doesn't look great. :) But let's go to the concretes. The goal of this chapter is to show that the Adapter Design Pattern can help you not only when you have to adapt a class from the external library which cannot be modified.
Imagine that you have a similar situation as in the previous example. Application is using the DatabaseLogger
hidden behind the ILogger
interface to log some events and exceptions.
Now you've got a task from your team leader that when the application will catch an exception, the e-mail message with exception details has to be sent to a special group of e-mail addresses. But.. the information that an event occurred has to be logged in the same way as before – in the database. Furthermore, for the e-mail logging feature, you have to use a common implementation used in the company, the EmailLogger
:
public class EmailLogger : IEmailLogger
{
public void SendError(Exception ex)
{
}
}
which implements IEmailLogger
interface:
public interface IEmailLogger
{
void SendError(Exception ex);
}
The EmailLogger
implementation is placed in a separate project to share it between the applications.
Now, you can modify the EmailLogger
class in every way you want, because it’s a custom implementation. But... Will it be the best solution in this case? The EmailLogger
code is used by many applications and how will it look like if every developer responsible for his application will modify its code to align the logger to his application?
Not so good. :)
Another thing is that the implementation of the ILogger
interface now has to support database logging and e-mail logging at the same time.
Next thing is that even if you will decide to make the EmailLogger
class implement ILogger
interface which is used by your application, the EmailLogger
library will have to reference your application (project) and your application(project) will have to reference EmailLogger
project to know implementation details of the EmailLogger
class. It will end up with the circular dependencies. To resolve this issue, you will have to introduce another project with interfaces.
I have a better solution for that. Just implement the LoggerAdapter
class (which will implement ILogger
interface):
public class LoggerAdapter : ILogger
{
private readonly EmailLogger _emailLogger;
private readonly DatabaseLogger _databaseLogger;
public LoggerAdapter()
{
_emailLogger = new EmailLogger();
_databaseLogger = new DatabaseLogger();
}
public void LogError(Exception ex)
{
_emailLogger.SendError(ex);
}
public void LogInfo(string message)
{
_databaseLogger.LogInfo(message);
}
}
and tell your application to use it as an implementation of the ILogger
interface (while configuring the dependency injection).
VOILA! Third problem solved!!!
Fourth Example: Adapt An Old Static Class to A New Code
Next situation – you are still an unhappy developer who maintains an old system.
One of your previous colleagues wrote a huge class with a lot of complex business logic and made it static
. The class is used in a lot of places in your application. Before there will be a decision made to refactor this class,you don’t want to touch the code inside it.
A static
class looks like below:
public static class StaticClass
{
public static decimal FirstComplexStaticMethod()
{
}
public static decimal SecondComplexStaticMethod()
{
}
}
and is used in many classes in your application, below on of the classes:
public class SampleClass
{
public void SampleMethod()
{
var resultOfComplexProcessing = StaticClass.FirstComplexStaticMethod();
var anotherResultOfComplexProcessing = StaticClass.SecondComplexStaticMethod();
}
}
Now you have to implement a new module, but you need to use a complex logic inside this class. At the same time, you want to write a new, clean code without consuming large static
classes – what would make your code untestable (I mean unit testing) and will make your class tightly coupled with the old static
class. You cannot hide this problematic class behind the interface, because static
class cannot implement any interface. But you don’t want to make your class tightly coupled with the old static
class. As I’ve mentioned before, you don’t want to change an implementation of the static
class as you don’t want to break other, old, working features.
So you don’t want your new quality code to look like below class:
public class NewCleanModule
{
public void SampleMethod()
{
var resultOfComplexProcessing = StaticClass.FirstComplexStaticMethod();
}
}
You are blocked!
The solution is very simple, just use the Adapter Design Pattern. :)
Your new, pretty class after applying the Adapter Pattern may look like the class below:
public class NewCleanModule
{
private readonly IComplexLogic _complexLogic;
public NewCleanModule(IComplexLogic complexLogic)
{
_complexLogic = complexLogic;
}
public void SampleMethod()
{
var resultOfComplexProcessing = _complexLogic.FirstComplexMethod();
}
}
What we have done here?
I’ve introduced the IComplexLogic
interface:
public interface IComplexLogic
{
decimal FirstComplexMethod();
}
which exposes only one method – the one which we need for our new, clean class – read: Interface Segregation Principle from SOLID principles.
The implementation of this interface will be injected into our new class, what will give us ability to create unit tests for our new class and will allow us to replace an old implementation of the old logic with the refactored one without changing the caller.
Nice, nice but ... We still have a problem. Our old static
class cannot implement any interface. So how can we inject an object of this class as an implementation of the IComplexLogic
interface?
Here comes the Adapter Design Pattern!
The code below presents how we can adapt our old StaticClass
to a new module:
public class StaticClassAdapter : IComplexLogic
{
public decimal FirstComplexMethod()
{
return StaticClass.FirstComplexStaticMethod();
}
}
This approach can help you in step by step refactoring of your applications. You will be able to write a new, clean code in an application which has a lot of badly written old code.
Conclusion
The Adapter pattern is very simple, but it can make your life much easier when you want to achieve a clean code. Sometimes it’s hard to find out that it could resolve your problem easily. I hope that you've learned from this article about at least one new usage of it. Even if you have used something similar to the Adapter Design Pattern in the past without knowledge that this pattern exists, it is good to be aware of that.
Why?
Because it will make the communication in a team much easier. It's faster and easier to say to your colleague that you've used the Adapter Pattern to implement ticket A rather than explaining: “I created class X which is implementing existing interface Y and in this class I initialized class Z and in an implementation of method A of class X I'm calling method B from class Z”.
Good luck colleagues!
If you have some questions related to the article, don't hesitate to contact me!
Sources
Sources used in the articles images:
Sources to the licenses used in the article images: