Click here to Skip to main content
15,881,852 members
Articles / Programming Languages / C#

C#: How the Adapter Design Pattern Can Make Your Life Easier

Rate me:
Please Sign up or sign in to vote.
4.82/5 (48 votes)
16 Jul 2016CPOL11 min read 50.2K   53   26
4 extremely practical examples of using the Adapter Design Pattern

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:

C#
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:

C#
public interface IFileService
{
    void SaveFile(string path, byte[] content);
}

and change our FileSystemManager to UnitTestableFileSystemManager:

C#
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:

C#
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:

C#
public interface ILogger
{
    void LogError(Exception ex);
    void LogInfo(string message);
}

so the code of the particular classes in the application looks like below:

C#
public class SampleClassOne
{
    private readonly ILogger _logger;
    public SampleClassOne(ILogger logger)
    {
        _logger = logger;
    }
 
    public void SampleMethod()
    {
        // some code
        _logger.LogInfo("User was added!");
        // some code
        _logger.LogInfo("Email was sent!");
        // some code
    }
}
 
 
public class SampleClassTwo
{
    private readonly ILogger _logger;
    public SampleClassTwo(ILogger logger)
    {
        _logger = logger;
    }
    public void SampleMethod()
    {
        try
        {
            // some code
            _logger.LogInfo("File was saved!");
        }
        catch (Exception ex)
        {
            _logger.LogError(ex);
        }
    }
}

and this is the implementation of the DatabaseLogger class:

C#
public class DatabaseLogger : ILogger
{
    public void LogError(Exception ex)
    {
        // code responsible for storing an exception in the database
    }
 
    public void LogInfo(string message)
    {
        // code responsible for storing an information in the database
    }
}

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:

C#
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:

C#
_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:

C#
public class EmailLogger : IEmailLogger
{
    public void SendError(Exception ex)
    {
        // code responsible for sending an exception on e-mail address
    }
}

which implements IEmailLogger interface:

C#
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):

C#
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:

C#
public static class StaticClass
{
  public static decimal FirstComplexStaticMethod()
  {
    // complex logic
  }

  public static decimal SecondComplexStaticMethod()
  {
    // complex logic
  }
}

and is used in many classes in your application, below on of the classes:

C#
public class SampleClass
{
  public void SampleMethod()
  {
    // some code
    var resultOfComplexProcessing = StaticClass.FirstComplexStaticMethod();
    // some code
    var anotherResultOfComplexProcessing = StaticClass.SecondComplexStaticMethod();
    // some code
  }
}

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:

C#
public class NewCleanModule
{
  public void SampleMethod()
  {
    // some code
    var resultOfComplexProcessing = StaticClass.FirstComplexStaticMethod();
    // some code
  }
}

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:

C#
public class NewCleanModule
{
  private readonly IComplexLogic _complexLogic;
  public NewCleanModule(IComplexLogic complexLogic)
    {
    _complexLogic = complexLogic;
    }
  public void SampleMethod()
  {
    // some code
    var resultOfComplexProcessing = _complexLogic.FirstComplexMethod();
    // some code
  }
}

What we have done here?

I’ve introduced the IComplexLogic interface:

C#
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:

C#
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:

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Ireland Ireland
Passionate, Microsoft Certified Software Developer(Senior),
having Masters Degree in Information Technology.

Comments and Discussions

 
Questiongood article Pin
mhars pabalan30-Jul-18 18:36
mhars pabalan30-Jul-18 18:36 
GeneralMy vote of 5 Pin
Dileep Mada11-Dec-16 5:43
professionalDileep Mada11-Dec-16 5:43 
GeneralRe: My vote of 5 Pin
Radosław Sadowski11-Dec-16 21:18
Radosław Sadowski11-Dec-16 21:18 
Questionshort and to the point Pin
buckrogerz18-Jul-16 9:17
buckrogerz18-Jul-16 9:17 
AnswerRe: short and to the point Pin
Radosław Sadowski19-Jul-16 11:36
Radosław Sadowski19-Jul-16 11:36 
QuestionAnd just another typical CodeProject article Pin
Издислав Издиславов16-Jul-16 23:33
Издислав Издиславов16-Jul-16 23:33 
QuestionNice Post Pin
PrasannaMurali16-Jul-16 19:49
professionalPrasannaMurali16-Jul-16 19:49 
AnswerRe: Nice Post Pin
Radosław Sadowski19-Jul-16 11:33
Radosław Sadowski19-Jul-16 11:33 
PraiseNice post! Pin
Member 1263835116-Jul-16 5:34
Member 1263835116-Jul-16 5:34 
GeneralRe: Nice post! Pin
Radosław Sadowski16-Jul-16 5:57
Radosław Sadowski16-Jul-16 5:57 
GeneralMy vote of 5 Pin
Manoj Kumar Choubey13-Jul-16 5:26
professionalManoj Kumar Choubey13-Jul-16 5:26 
GeneralRe: My vote of 5 Pin
Radosław Sadowski13-Jul-16 9:40
Radosław Sadowski13-Jul-16 9:40 
PraiseThanks for the reminder! Pin
bojammis12-Jul-16 6:46
professionalbojammis12-Jul-16 6:46 
GeneralRe: Thanks for the reminder! Pin
Radosław Sadowski12-Jul-16 21:26
Radosław Sadowski12-Jul-16 21:26 
SuggestionGood , i prefer 1 complete example than 5 incomplete as a learning method . Pin
boaz levinson12-Jul-16 6:42
boaz levinson12-Jul-16 6:42 
GeneralRe: Good , i prefer 1 complete example than 5 incomplete as a learning method . Pin
Radosław Sadowski12-Jul-16 22:10
Radosław Sadowski12-Jul-16 22:10 
GeneralRe: Good , i prefer 1 complete example than 5 incomplete as a learning method . Pin
boaz levinson13-Jul-16 23:28
boaz levinson13-Jul-16 23:28 
PraiseGreat Article! Pin
Shai Aharoni12-Jul-16 2:00
Shai Aharoni12-Jul-16 2:00 
GeneralRe: Great Article! Pin
Radosław Sadowski12-Jul-16 21:24
Radosław Sadowski12-Jul-16 21:24 
PraiseExcellent article Pin
DalalV11-Jul-16 0:24
DalalV11-Jul-16 0:24 
GeneralRe: Excellent article Pin
Radosław Sadowski12-Jul-16 21:24
Radosław Sadowski12-Jul-16 21:24 
Suggestiongood article but... Pin
Klaus Luedenscheidt7-Jul-16 18:43
Klaus Luedenscheidt7-Jul-16 18:43 
GeneralRe: good article but... Pin
Radosław Sadowski7-Jul-16 21:12
Radosław Sadowski7-Jul-16 21:12 
PraiseNice reading Pin
AmitMukherjee6-Jul-16 21:51
AmitMukherjee6-Jul-16 21:51 
GeneralRe: Nice reading Pin
Radosław Sadowski7-Jul-16 2:10
Radosław Sadowski7-Jul-16 2:10 
Hi Amit,
Thank you very much for reading and for your kind words.

Thank you,
Radoslaw Sadowski

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.