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; }
}
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
}
}
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();
}
}
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.
}
}
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();
}
}
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();
}
}
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();
}
}
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
callingAdapterB
callingAdapterC
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.