Design Patterns - Adapter

Problem

Incompatible interfaces between a client and a service provider.

Adapters convert the interface of one class into an interface a client expects.

Two Kinds of Adapters

Object Adapters

  • Hold an instance of the Adaptee
  • Implement or inherit the adapter type
  • Use composition and single inheritance

C# doesn’t support multiple inheritance, and a design principle of C# is to prefer composition over inheritance. So C# is prefer object adapter.

The Client is calling method on an adapter abstraction(IAdapter). A specific adapter is created for each specific adaptee.

Class Adapters

  • Inherit from the adaptee
  • Implement the adapter interface

The Client is calling a target class’s particular method, but it wants to use a different implementation(incompatibleMethod) now. The adapter class inherits from both classes and overrides the SomeMethod() call, so that instead of doing what it did in the target class, it now calls the IncompatibleMethod() and does any work necessary to make it compatible with the SomeMethod() interface.

What C# can do is to implement the interface the Client is calling and rather than holding onto an instance of the concrete adaptee type, we can inherit from it. SomeMethod() calls IncompetibleMethod() and does any necessary work to modify it to work with the SomeMethod() interface.

Example: We are going to read a list of People and there are two ways of doing it. First, we could read People from a file. Second, we could call a Web API.

IAdapter interface will only have a method, GetCharacters(), get it returns a list of Peoson

1
2
3
4
public interface ICharacterSourceAdapter
{
Task<IEnumerable<Person>> GetCharacters();
}

Get list of Person from a web API is easy.

1
2
3
4
5
6
7
8
9
10
11
public async Task<List<Person>> GetCharacters()
{
using (var client = new HttpClient())
{
string url = "https://swapi.co/api/people";
string result = await client.GetStringAsync(url);
var people = JsonConvert.DeserializeObject<ApiResult<Person>>(result).Results;

return people;
}
}

But get list of Person from a file need a parameter (filename)

1
2
3
4
5
6
public async Task<List<Person>> GetCharactersFromFile(string filename)
{
var characters = JsonConvert.DeserializeObject<List<Person>>(await File.ReadAllTextAsync(filename));

return characters;
}

To make it work with the GetCharacters() method, we need to create an Adapter Class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class CharacterFileSourceAdapter : ICharacterSourceAdapter
{
private string _fileName;
private readonly CharacterFileSource _characterFileSource;

public CharacterFileSourceAdapter(string fileName, CharacterFileSource characterFileSource)
{
_fileName = fileName;
_characterFileSource = characterFileSource;
}

public async Task<IEnumerable<Person>> GetCharacters()
{
return await _characterFileSource.GetCharactersFromFile(_fileName);
}
}

It implements the IAdapter interface, and inside GetCharacters() method, it calls the GetCharactersFromFile(_filename) method to make it compatible with our interface method.

When we use it, it doesn’t need to know anything about the filename or which way we choose to get the list of Person.

1
var people = await _characterSourceAdapter.GetCharacters();
  • Decorator: has a similar structure, but the intent of a decorator is to add functionality.

  • Bridge: has a similar structure, but it allows interfaces and their implementations to vary independently from one another.

  • Proxy: similar structure, but its intent is to control access to a resource, not to convert an incompatible interface

  • Repository: sometimes it acts an adapter, providing a common interface for persistence that can map various incompatible interfaces to a single common data access strategy

  • Strategy: very frequently used with Adapter pattern as a way of injecting different implementations of behavior into a particular client class.

  • Facade: the intent of facade is similar to the adapters in that it alters an interface to make it easier for a client to use. The difference is Facade often sits in front of multiple different types and its goal is to simplify a complex set of operation

Summary

  • An Adapter converts an incompatible interface into a compatible one
  • In C#, the Adapter pattern uses composition and is known as an object adapter. It means that your adapter implementation will contain instances of the incompatible type and will delegate calls to this instalce’s incompatible methods or properties.
  • Adapters can work with service providers but can also wrap result types.