In this article, we discuss the Adapter design pattern, which is part of the book “Design Patterns: Elements of Reusable Object-Oriented Software” by Gamma et al. (also known as the Gang of Four).
We will discuss the motivation for this design pattern (including common misconceptions) and see a number of different ways in which this pattern is implemented in C#.
TL;DR Use extension methods for your mapping code.
Defining the Interface of a Class
The above book summaries the Adapter pattern thusly:
“Convert the interface of a class into another interface clients expect. Adapter lets classes work together that couldn’t otherwise because of incompatible interfaces.”
It is fundamental to interpret this description in the context in which it was written. The book was published in 1994, before Java and similar languages even existed (in fact, the examples are in C++). Back then, “the interface of a class” simply meant its public API, to which extent “client code” could use it. C++ has no formal interface construct as in C# and Java, and the degree of encapsulation is dictated as a result of what the class exposes publicly. Note that a class’s interface is not restricted to its public methods alone; C++ also offers other devices external to the class itself, and so does C# (extension methods, for instance).
Given today’s languages where interfaces are formal language constructs, such an interpretation has mostly been forgotten. For instance, the 2013 MSDN Magazine article “The Adapter Pattern in the .NET Framework” illustrates the Adapter design pattern in terms of C# interfaces, but it slightly misses the point. We can discuss the Adapter pattern without referring to C# interfaces at all, and instead focus on Data Transfer Objects (DTOs). DTOs are simply lightweight classes used to carry data, often between remote applications.
An Example Scenario
Let’s say we have this third party class with a single method:
public class PersonRepository { public void AddPerson(Person person) { // implementation goes here } }
The interface of this class consists of the single AddPerson()
method, but the Person class that it expects as a parameter is also part of that interface. There is no way that the third party could give us the PersonRepository (e.g. via a NuGet package) without also including the Person class.
It is often the case that we may have a different Person class of our own (we’ll call it OurPerson), but we can’t pass it to AddPerson()
, because it expects its own Person class:
public class Person // third party class { public string FullName { get; set; } public Person(string fullName) { this.FullName = fullName; } } public class OurPerson // our class { public string FirstName { get; set; } public string LastName { get; set; } public OurPerson(string firstName, string lastName) { FirstName = firstName; LastName = lastName; } }
Thus, we need a way to transform an OurPerson instance into a Person. For that, we need an Adapter. Next, we’ll go through a few ways in which we can implement this.
Constructor Adapter
One way of creating a Person from an OurPerson is to add a constructor in Person which handles the conversion:
public class Person // third party class { public string FullName { get; set; } public Person(string fullName) { this.FullName = fullName; } public Person(OurPerson ourPerson) { this.FullName = $"{ourPerson.FirstName} {ourPerson.LastName}"; } }
It is not hard to see why this is a really bad idea. It forces Person to have a direct dependency on OurPerson, so anyone shipping PersonRepository would now need to ship an additional class that may be domain-specific and might not belong in this context. Even worse, this is not possible to achieve if the Person class belongs to a third party and we are not able to modify it.
Wrapper
Another approach (which the aforementioned book describes) is to implement OurPerson in terms of Person. This can be done by subclassing, if the third party class allows it:
public class OurPerson : Person // our class { public string FirstName { get; set; } public string LastName { get; set; } public OurPerson(string firstName, string lastName) : base($"{firstName} {lastName}") { FirstName = firstName; LastName = lastName; } }
Where C# interfaces are involved, an alternative approach to inheritance is composition. OurPerson could contain a Person instance and expose the necessary methods and properties to implement its interface.
The disadvantage of either of these two approaches is that they make OurPerson dependent on Person, which is the opposite of the problem we have seen in the previous approach. This dependency would be carried to wherever OurPerson is used.
Especially when dealing with third party libraries, it is usually best to map their data onto our own objects. Any changes to the third party classes will thus have limited impact in our domain.
AutoMapper
A lot of people love to use AutoMapper for mapping DTOs. It is particularly useful if the source and destination classes are practically identical in terms of properties. If they’re not, you’ll have to do a fair amount of configuration to tell AutoMapper how to construct the destination properties from the data in the source.
Personally, I’m not a fan of AutoMapper, for the following reasons:
- The mapping occurs dynamically at runtime. Thus, as the DTOs evolve, you will potentially have runtime errors in production. I prefer to catch such issues at compile-time.
- Writing AutoMapper configuration can get very tedious and unreadable, often more so than it would take to map DTOs’ properties manually.
- You can’t do anything asynchronous in AutoMapper configuration. While this may sound bizarre, I’ve needed this in the past due to terrible DTOs provided by a third party provider.
Standalone Adapter
The aforementioned MSDN Magazine article simply uses a separate class to convert from source to destination. Applied to our example, this could look like this:
public class PersonAdapter { public Person ConvertToPerson(OurPerson person) { return new Person($"{person.FirstName} {person.LastName}"); } }
We may then imagine its usage as such:
var ourPerson = new OurPerson("Chuck", "Berry"); var adapter = new PersonAdapter(); var person = adapter.ConvertToPerson(ourPerson);
This approach is valid, and does not couple the DTOs together, but it is a little tedious in that you may have to create a lot of adapter classes, and subsequently create instances whenever you want to use them. You can mitigate this a little by making them static.
Extension Methods
A slight but very elegant improvement over the previous approach is to put mapping code into extension methods.
public static class OurPersonExtensions { public static Person ToPerson(this OurPerson person) { return new Person($"{person.FirstName} {person.LastName}"); } }
The usage is very clean, making it look like the conversion operation is part of the interface of the class:
var ourPerson = new OurPerson("Chuck", "Berry"); var person = ourPerson.ToPerson();
This works just as well if you’ve converting from a third party object onto your own class, since extension methods can be used to append functionality even to classes over which you have no control.
Summary
There are many ways to apply the Adapter design pattern in mapping DTOs. Extension methods are the best approach I’ve come across for C# because:
- They don’t couple the source and destination DTOs.
- The conversion occurs at compile time, so any repercussions of changes to the DTOs will be caught early.
- They can easily be used to append functionality even to third party classes for which the source code is not available.
The Adapter design pattern has little to do with interfaces as a formal OOP language construct. Instead, it deals with “the interface of a class”, which is embodied by whatever it exposes publicly. The Adapter design pattern provides a means to work with that interface by converting incompatible objects to ones that satisfy its contract.
I liked the way you outlined the different approaches to adapt DTO. What i follow is the modified 3rd approach to define a separate Adapter class. An interface\abstract class with
TOutput convert(TInput input)
to represent an adapter and then specific adapter implementing this interface to act as an adapter. It becomes extensible, pluggable.
Extensions are good, however these are just static methods, are a form of singleton. Imagine if you have to adapt Person to different versions of OurPerson. This will start cluttering.
I really like this approach. I appreciate and agree with your comments about Automapper. Thanks for taking time to publish your experience on this subject. I’m going to apply this approach and, in fact, have had some experience with some very difficult Automapper conversions, and found that this approach is much easier to understand and can be much more readable to those developers that have not had a great deal of experience with Automapper.
If Adapter adapts one existing interface to another existing one and Mapper creates a new object based on an existing one, you basically created PersonMapper and not PersonAdapter.
What exactly is the difference? In the article, I have already explained the interpretation of “interface” in the context of this design pattern.