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#!