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: HelloThe 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
EasyLet'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 aLegacyAudioPlayerclass in theMediaSystemnamespace with a methodPlayMp3(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 anIMediaPlayerinterface with a methodPlay(string filename)that returns a string. Then create aMediaPlayerAdapterclass that implementsIMediaPlayer. Your adapter should wrap aLegacyAudioPlayerinstance (passed through the constructor) and translate thePlaycall to the legacy player'sPlayMp3method.Program.cs: Bring everything together by creating the legacy player, wrapping it in your adapter, and using the adapter through theIMediaPlayerinterface. 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.mp3The 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: HelloThe 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
}
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Fundamentals of OOP
External FilesNamespaces & DirectivesIntro to Classes & ObjectsThe 'this' KeywordMethods and ParametersFields vs PropertiesConstructorsObject InitializersRecap - Simple Calculator4Inheritance
Basic Inheritance (:) SyntaxThe 'base' KeywordVirtual & Override KeywordsSealed ClassesThe 'object' Base ClassRecap - Employee Hierarchy7Advanced Features
Operator OverloadingIndexers (this[])ToString() OverrideExtension MethodsRecap - Custom List2Properties & Static Members
Auto-Implemented PropertiesRead/Write-Only PropertiesStatic Fields & MethodsStatic ClassesExpression-Bodied Members5Polymorphism & Interfaces
Compile vs Runtime PolyInterface vs Abstract ClassMultiple InterfacesExplicit InterfacesUpcasting & DowncastingRecap - Shape Calculator8Advanced OOP Concepts
Composition over InheritanceGenerics (Classes & Methods)Delegates and EventsAttributes and ReflectionIDisposable & using StatementDependency Injection Basics11Design Patterns Part 2
Command PatternAdapter PatternDecorator PatternTemplate Method PatternState PatternComposite Pattern