This is part of a series of posts demonstrating software design patterns using C# and .NET. The patterns are taken from the book Design Patterns by the Gang of Four. Here's the series index page.

What Is This Pattern?

The Adapter design pattern attempts to reconcile the differences between two otherwise-incompatible interfaces. 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).

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.

A variety of cuts of meat in a supermarket display case.

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. Such a system might look like this (the Adaptee participant):

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 object (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. 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(), 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:

The output of the sample Adapter pattern project, showing that the non-adapted class displays no data but the adapted classes do.

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 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, and is especially useful when one or both of those interfaces cannot be refactored.

As always, I like to provide code with my tutorials, so the repository for this pattern is over on GitHub and contains all of the sample code used here.

Now, if you'll excuse me, I think I need to go grill something.

Happy Coding!

Get My eBook FREE!

Did you enjoy this post? This article and 21 others are also in my free eBook, "The Daily Design Pattern", which you can get just by signing up for my email list.