DEV Community

Cover image for How to Use the Excellent Adapter Pattern and Why You Should
Kyle Galbraith
Kyle Galbraith

Posted on • Updated on • Originally published at blog.kylegalbraith.com

How to Use the Excellent Adapter Pattern and Why You Should

The adapter pattern is classified as a structural pattern that allows a piece of code talk to another piece of code that it is not directly compatible with.

First, for the sake of the next few minutes let's frame our context within the bounds of a web application we are responsible for. The application is a classic three-tier application, front-end client, web server for the API, and a place to store data. This is a pretty traditional stack nowadays.

Now I want to call out: a place to store data. This could be a database like SQL Server or MongoDB. It could also just be a place we dump data like AWS S3 or maybe even our hard drive.

It is preferable if our application never even cares where we store or read data. If we define a common interface for doing those operations we can change where we store or read data from without the application needing to change. For that, we leverage something the repository pattern.

The adapter pattern is a pattern that could be used within the repository. It is a pattern that allows your application code to leverage a consistent interface for working with another piece of code without needing to be reliant on that code. To put it even more bluntly for this post, we are going to define an adapter as a common interface for connecting two pieces of disjointed code.

With me so far? It's ok if your not, let's look at when you might use it to try and clear things up.

Spotting the (potential) need for the adapter pattern

To be franc spotting the potential use of the adapter pattern can sometimes be harder than it sounds. This is often because most modern programming languages already have adapters built into them. But let's pretend we have an IPerson interface as defined below.

public interface Person : IPerson
{
    public string Name { get; set; }
    public string City { get; set; }
    public string IdNumber { get; set; }
}
Enter fullscreen mode Exit fullscreen mode

We then have an old piece of code that has always been responsible for loading the Person objects.

public class PersonLoader: IPersonLoader
{
    public Person LoadPerson()
    {
        //code to load a person
    }
}
Enter fullscreen mode Exit fullscreen mode

Notice here that PersonLoader implements a common interface IPersonLoader that defines the necessity for a method called LoadPerson(). Let's look at the code that is using PersonLoader.

public class LoadPeople
{
    static void Main(string[] args)
    {
        IPersonLoader loader = new PersonLoader();
        var person = loader.LoadPerson();
    }
}
Enter fullscreen mode Exit fullscreen mode

Here the loader is of type IPersonLoader, and PersonLoader just happens to implement that interface. So a PersonLoader is created, and the code can then call LoadPerson(). So far so good right?

But remember, PersonLoader is a crummy piece of code that we want to move away from without breaking our entire application. Specifically, we want to start loading the IPerson objects using a new and improved loader.

What we have here is a great use case for the adapter pattern.

Adding in the adapter spice

First, we define our new and improved person loader, BetterPersonLoader that implements IBetterPersonLoader which contains a new method RunGetPerson().

public class BetterPersonLoader : IBetterPersonLoader
{
    public Person RunGetPerson()
    {
        //code to get person the new and improved way.
    }
}
Enter fullscreen mode Exit fullscreen mode

But we can't just plug in BetterPersonLoader into our LoadPeople client. It's not compatible and would break that code because that code needs an IPersonLoader interface.

So what we can do is define a PersonAdapter that implements that interface. Let's see what that would look like.

public class PersonAdapter : IPersonLoader
{   
    public Person LoadPerson()
    {
        var newLoader = new BetterPersonLoader();
        return newLoader.RunGetPerson();
    }
}
Enter fullscreen mode Exit fullscreen mode

We can then update our LoadPeople client to leverage our new adapter.

public class LoadPeople
{
    static void Main(string[] args)
    {
        IPersonLoader loader = new PersonAdapter();
        var person = loader.LoadPerson();
    }
}
Enter fullscreen mode Exit fullscreen mode

Now, this code is resilient to this change again in the future. If BestPersonLoader comes along, we can update our PersonAdapter class and the LoadPeople client never needs to be changed.

public class PersonAdapter : IPersonLoader
{   
    public Person LoadPerson()
    {
        var newLoader = new BestPersonLoader();
        return newLoader.BestWayToGetPerson();
    }
}
Enter fullscreen mode Exit fullscreen mode

The pros and the cons

The adapter pattern shows up in a lot of different flavors. You can see it in something as simple as loading data from somewhere. Or you can see it in more complex implementations like in an HTTP client. The adapter pattern introduces a lot of nice benefits:

  • It increases reusability and flexibility. The interface of the adapter is defined and agreed upon. So as long as the agreement is maintained we can change the implementation within the adapter.
  • Clients become simplified. As we saw in our example after the refactor to an adapter pattern we can now change to any kind of loader logic we want. The client doesn't have to make that decision because the adapter has agreed to the IPersonLoader contract.
  • Changing and trying new ideas, breaks and changes fewer things. The coupling is minimized to just the agreed upon contract, the implementation of that contract is free to interpretation.

These are stellar benefits, but I would reminisce if I didn't warn you of the potential traps you could fall into.

  • The rabbit trail effect. Sometimes adapters can be taken to the extreme if you have deeply nested objects. You can end up with AdapterA calling AdapterB calling AdapterC just to load a Person.
  • Prone to over-engineering. Notice in this post I started with a piece of code that didn't use the adapter pattern. This is because it is a pattern that often emerges and is not known from the outset. Do your best to not try and introduce an adapter pattern when you don't actually need one.

Hungry To Learn Amazon Web Services?

There is a lot of people that are hungry to learn Amazon Web Services. Inspired by this fact I have created a course focused on learning Amazon Web Services by using it. Focusing on the problem of hosting, securing, and delivering static websites. You learn services like S3, API Gateway, CloudFront, Lambda, and WAF by building a solution to the problem.

There is a sea of information out there around AWS. It is easy to get lost and not make any progress in learning. By working through this problem we can cut through the information and speed up your learning. My goal with this book and video course is to share what I have learned with you.

Sound interesting? Check out the landing page to learn more and pick a package that works for you, learn AWS basics by actually using it.

Top comments (1)

Collapse
 
wintermute21 profile image
John Best

s/franc/frank