Design patterns in C# - The Adapter Pattern
Hello again, in this post I'm continuing my series on design patterns in C#, this time focusing on the adaptor pattern!
The adapter pattern is useful when you want to use a class that does not fit the design of your existing solution. This is often the case when using legacy or external code. The adapter pattern allows you to define a wrapper which executes the desired behaviour, but exposes it through a method which your solution expects.
As always, I think this will become clearer when we delve into our (dinosaur-based) example.
Adapter Pattern Example
So, today we've been transported to a post-meteor world...
And in this world, in the age of mammals, we have a ChildCreator
that expects things to work in a certain way:
using System;
using System.Collections.Generic;
using System.Text;
namespace DesignPatterns.Adapter
{
public static class ChildCreator
{
public static IChild CreateChild(IMammal mammal)
{
return mammal.GiveBirth();
}
}
}
Where the IMammal
interface looks like this:
using System;
using System.Collections.Generic;
using System.Text;
namespace DesignPatterns.Adapter
{
public interface IMammal
{
IChild GiveBirth();
}
}
The GiveBirth
method produces an IChild
, which has a single method, Cry
(which I'm pretty sure is the main functionality of a new born baby, right?).
namespace DesignPatterns.Adapter
{
public interface IChild
{
void Cry();
}
}
However, though the world has moved on, we have a few legacy Triceratops
hanging around, that are just trying to get along in an unfamiliar place... Unfortunately a Triceratops
works somewhat differently to the majority of mammals (being a reptile and all), possessing a single LayEgg
method:
using System;
using System.Collections.Generic;
using System.Text;
namespace DesignPatterns.Adapter
{
public class Triceratops
{
public TriceratopsEgg LayEgg()
{
return new TriceratopsEgg();
}
}
}
The TriceratopsEgg
produced by this method then has the ability to hatch into a TriceratopsChild
:
namespace DesignPatterns.Adapter
{
public class TriceratopsEgg
{
public IChild Hatch()
{
return new TriceratopsChild();
}
}
}
And here is where our adapter comes in. We need a way that our ChildCreator
can work with the Triceratops
to produce a child.
If we define a TriceratopsToMammalAdapter
, which implements the IMammal
class, and wraps an internal Triceratops
:
using System;
using System.Collections.Generic;
using System.Text;
namespace DesignPatterns.Adapter
{
public class TriceratopsToMammalAdapter : IMammal
{
private readonly Triceratops triceratops;
public TriceratopsToMammalAdapter(Triceratops triceratops)
{
this.triceratops = triceratops;
}
public IChild GiveBirth()
{
TriceratopsEgg egg = triceratops.LayEgg();
IChild child = egg.Hatch();
return child;
}
}
}
Then the GiveBirth
method can call the LayEgg
method of the internal Triceratops
, and then Hatch
that egg to produce an IChild
. We then return the child, and the Triceratops
is able to continue to function in the new mammalian landscape.
If we now use our TriceratopsToMammalAdapter
and ChildCreator
:
public static void AdapterExample()
{
var triceratops = new Triceratops();
var child = ChildCreator.CreateChild(new TriceratopsToMammalAdapter(triceratops));
child.Cry();
}
We can produce a healthy, crying, TriceratopsChild
, with the output: TRICERATOPS IS CRYING
! (Granted, as the lack of modern triceratops indicates, this probably isn't a long term solution and eventually that legacy code will likely still need to be replaced...)
Thanks for reading! Here's a link to the GitHub repository which contains all the code for this blog series. And watch of for my next blog on design patterns in C#!