Menu
Coddy logo textTech

Adapter Pattern

Part of the Object Oriented Programming section of Coddy's C# journey — lesson 57 of 70.

The Adapter pattern is a structural pattern that allows incompatible interfaces to work together. It acts as a bridge between two classes, converting the interface of one class into an interface that clients expect. Think of it like a power adapter that lets you plug a European device into an American outlet.

This pattern is particularly useful when you want to use an existing class but its interface doesn't match what your code needs. Instead of modifying the original class (which you might not control), you create an adapter that wraps it:

// Existing class with incompatible interface
public class OldPrinter
{
    public void PrintMessage(string msg) => Console.WriteLine($"Old: {msg}");
}

// Interface your code expects
public interface IPrinter
{
    void Print(string text);
}

// Adapter bridges the gap
public class PrinterAdapter : IPrinter
{
    private OldPrinter _oldPrinter;
    
    public PrinterAdapter(OldPrinter oldPrinter)
    {
        _oldPrinter = oldPrinter;
    }
    
    public void Print(string text)
    {
        _oldPrinter.PrintMessage(text);  // Delegates to old interface
    }
}

Now your code can work with the old printer through the expected interface:

OldPrinter legacy = new OldPrinter();
IPrinter printer = new PrinterAdapter(legacy);

printer.Print("Hello");  // Old: Hello

The Adapter pattern is invaluable when integrating third-party libraries or legacy code into your application. The client code works with the IPrinter interface, completely unaware that an old, incompatible class is doing the actual work behind the scenes.

challenge icon

Challenge

Easy

Let's build a media player system using the Adapter pattern. You have an existing audio library that plays MP3 files, but your application needs a unified interface that can handle different media types. You'll create an adapter to bridge this gap, allowing your modern media player to work seamlessly with the legacy audio library.

You'll organize your code across three files:

  • LegacyAudio.cs: This represents an existing library you can't modify. Create a LegacyAudioPlayer class in the MediaSystem namespace with a method PlayMp3(string filename) that returns the string "Playing MP3: {filename}". This is your "adaptee" - the class with the incompatible interface.
  • MediaAdapter.cs: Create the interface and adapter in the same namespace. Define an IMediaPlayer interface with a method Play(string filename) that returns a string. Then create a MediaPlayerAdapter class that implements IMediaPlayer. Your adapter should wrap a LegacyAudioPlayer instance (passed through the constructor) and translate the Play call to the legacy player's PlayMp3 method.
  • Program.cs: Bring everything together by creating the legacy player, wrapping it in your adapter, and using the adapter through the IMediaPlayer interface. This demonstrates how client code can work with the modern interface while the adapter handles communication with the legacy system behind the scenes.

You will receive one input:

  • The filename to play (e.g., song.mp3)

Create a LegacyAudioPlayer, wrap it in a MediaPlayerAdapter, then call Play on the adapter using the IMediaPlayer interface and print the result.

For example, if the input is favorite_track.mp3, the output should be:

Playing MP3: favorite_track.mp3

The beauty of this pattern is that your main code only interacts with IMediaPlayer - it has no idea that a legacy audio library is doing the actual work. If you later need to support a different legacy library, you'd simply create a new adapter without changing any client code!

Cheat sheet

The Adapter pattern is a structural pattern that allows incompatible interfaces to work together by acting as a bridge between two classes.

The pattern converts the interface of one class into an interface that clients expect, useful when you want to use an existing class but its interface doesn't match your code's needs.

Basic structure:

// Existing class with incompatible interface (Adaptee)
public class OldPrinter
{
    public void PrintMessage(string msg) => Console.WriteLine($"Old: {msg}");
}

// Target interface your code expects
public interface IPrinter
{
    void Print(string text);
}

// Adapter bridges the gap
public class PrinterAdapter : IPrinter
{
    private OldPrinter _oldPrinter;
    
    public PrinterAdapter(OldPrinter oldPrinter)
    {
        _oldPrinter = oldPrinter;
    }
    
    public void Print(string text)
    {
        _oldPrinter.PrintMessage(text);  // Delegates to old interface
    }
}

Usage:

OldPrinter legacy = new OldPrinter();
IPrinter printer = new PrinterAdapter(legacy);

printer.Print("Hello");  // Old: Hello

The client code works with the target interface, unaware that an incompatible class is doing the actual work behind the scenes. This pattern is particularly valuable when integrating third-party libraries or legacy code.

Try it yourself

using System;
using MediaSystem;

class Program
{
    public static void Main(string[] args)
    {
        // Read the filename input
        string filename = Console.ReadLine();
        
        // TODO: Create a LegacyAudioPlayer instance
        
        // TODO: Wrap it in a MediaPlayerAdapter
        
        // TODO: Use the adapter through the IMediaPlayer interface
        // and print the result of calling Play with the filename
    }
}
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming