The Adapter pattern attempts to reconcile the differences between two otherwise-incompatible interfaces or classes. In doing so, it wraps one of the incompatible classes in a thin layer that allows it to now talk to the other class.
This pattern is especially useful when attempting to adapt to an interface which cannot be refactored (e.g. when the interface is controlled by a web service or API).
NOTE: This post is part of a series demonstrating software design patterns using C# and .NET. The patterns are taken from the book Design Patterns by the Gang of Four. Check out the other posts in this series.
The Rundown
- Type: Structural
- Useful? 4/5 (Very)
- Good For: Adapting two interfaces together when one or more of those interfaces cannot be refactored.
- Example Code: On GitHub
The Participants
- The Target defines the domain-specific interface in use by the Client.
- The Client collaborates with objects which conform to the Target.
- The Adapter adapts the Adaptee to the Target.
- The Adaptee is the interface that needs adapting (i.e. the one that cannot be refactored).
A Delicious Example
Vegetarians beware; for this demo, we're gonna cook some meat. Lots of it.
Let's imagine that we maintain a meat safe-cooking temperature database. The US Food & Drug Administration maintains a list of temperatures to which meat must be cooked before it is safe for human consumption.
For this demo, we'll say that we have an old, legacy system which stores this temperature data. This legacy system will be represented by the class MeatDatabase
and will be our Adaptee participant. Such a system might look like this:
public enum TemperatureType
{
Fahrenheit,
Celsius
}
/// <summary>
/// The legacy API which must be converted to the new structure
/// </summary>
class MeatDatabase
{
// Temps from http://www.foodsafety.gov/keep/charts/mintemp.html
public float GetSafeCookTemp(string meat, TemperatureType tempType)
{
if (tempType == TemperatureType.Fahrenheit)
{
switch (meat)
{
case "beef":
case "pork":
return 145f;
case "chicken":
case "turkey":
return 165f;
default:
return 165f;
}
}
else
{
switch (meat)
{
case "beef":
case "veal":
case "pork":
return 63f;
case "chicken":
case "turkey":
return 74f;
default:
return 74f;
}
}
}
public int GetCaloriesPerOunce(string meat)
{
switch (meat.ToLower())
{
case "beef": return 71;
case "pork": return 69;
case "chicken": return 66;
case "turkey": return 38; //Wow, turkey is lean!
default: return 0;
}
}
public double GetProteinPerOunce(string meat)
{
switch (meat.ToLower())
{
case "beef": return 7.33f;
case "pork": return 7.67f;
case "chicken": return 8.57f;
case "turkey": return 8.5f;
default: return 0d;
}
}
}
This legacy API does not properly model objects in an object-oriented fashion. Where this API returns results from methods, we know that that data (safe cook temperature, calories per ounce, protein per ounce) should really be properties in some kind of Meat
object.
So, let's create that Meat
class (the Target participant):
/// <summary>
/// The new Meat class, which represents
/// details about a particular kind of meat.
/// </summary>
class Meat
{
protected string MeatName;
protected float SafeCookTempFahrenheit;
protected float SafeCookTempCelsius;
protected double CaloriesPerOunce;
protected double ProteinPerOunce;
// Constructor
public Meat(string meat)
{
this.MeatName = meat;
}
public virtual void LoadData()
{
Console.WriteLine("\nMeat: {0} ------ ", MeatName);
}
}
Problem is, we cannot modify the legacy API, which is the MeatDatabase
class. Here's where our Adapter participant comes into play: we need another class that inherits from Meat
but maintains a reference to the API such that the API's data can be loaded into an instance of the Meat
class:
/// <summary>
/// The Adapter class, which wraps the Meat class
/// and initializes that class's values.
/// </summary>
class MeatDetails : Meat
{
private MeatDatabase _meatDatabase;
// Constructor
public MeatDetails(string name)
: base(name)
{
}
public override void LoadData()
{
// The Adaptee
_meatDatabase = new MeatDatabase();
SafeCookTempFahrenheit = _meatDatabase.GetSafeCookTemp(MeatName, TemperatureType.Fahrenheit);
SafeCookTempCelsius = _meatDatabase.GetSafeCookTemp(MeatName, TemperatureType.Celsius);
CaloriesPerOunce = _meatDatabase.GetCaloriesPerOunce(MeatName);
ProteinPerOunce = _meatDatabase.GetProteinPerOunce(MeatName);
base.LoadData();
Console.WriteLine(" Safe Cook Temp (F): {0}", SafeCookTempFahrenheit);
Console.WriteLine(" Safe Cook Temp (C): {0}", SafeCookTempCelsius);
Console.WriteLine(" Calories per Ounce: {0}", CaloriesPerOunce);
Console.WriteLine(" Protein per Ounce: {0}", ProteinPerOunce);
}
}
Finally, in our Main()
method, we can now show the different between using the Target class by itself and using the Adapter class:
static void Main(string[] args)
{
//Non-adapted
Meat unknown = new Meat("Beef");
unknown.LoadData();
//Adapted
MeatDetails beef = new MeatDetails("Beef");
beef.LoadData();
MeatDetails turkey = new MeatDetails("Turkey");
turkey.LoadData();
MeatDetails chicken = new MeatDetails("Chicken");
chicken.LoadData();
Console.ReadKey();
}
The output of this application looks like this:
Just like that, we've overcome the fact that we cannot refactor the legacy API, and now we can properly model these classes in an object-oriented style.
Will I Ever Use This Pattern?
Most likely. As I've been mentioning, the pattern is extremely useful when you're trying to adapt old or legacy systems to new designs, so if you're ever in that situation the Adapter pattern might be the best fit for your project.
Adapter Pattern vs. Facade Pattern
Adapter and Facade are very similar, but they are used in different ways.
- Facade creates a new interface; Adapter re-uses an existing one.
- Facade hides several interfaces; Adapter makes two existing ones work together.
- Facade is the equivalent of saying "This is never gonna work; I will build my own."; Adapter is the equivalent of "Of course it will work, it just needs a little tweaking."
Summary
The Adapter pattern attempts to reconcile two incompatible interfaces so that they can be used or called by one another, and is especially useful when one or both of those interfaces cannot be refactored.
Now, if you'll excuse me, I think I need to go grill something.
Happy Coding!