The code is available on GitHub here
The Adapter pattern is used to integrate two objects with incompatible interfaces together. The way the Adapter is able to bridge the two objects is through encapsulation and inheritance.
Adapters, deep dive
We use adapters in every day life and most of the time we are not even aware of them. For example, there are many types of USB and so are the adapters that bridge one type of USB to another. Another example is to connect a laptop with a VGA port to a monitor with an HDMI port. In that case, you cannot change either the laptop or the monitor, so the only solution is to use a VGA to HDMI adapter.
Adapters do not provide extra functionality, they just connect one interface to another. However, this doesn’t mean that adapters could not have complex internal logic. In general, as the two interfaces we want to bridge are more incompatible, the more internal logic the adapter might have.
Next, we dive into the pattern as we implement an Adapter for an AudioPlayerClient example.
The Audio Player Example
In our media player application we use the AudioPlayerClient that plays sound files. This object exposes functionality to Play and Stop various sound files. It uses the LegacyAudioPlayer that handles the low level stuff of playing the actual file.
public class AudioPlayerClient
{
private readonly LegacyAudioPlayer _audioPlayer;
public AudioPlayerClient(LegacyAudioPlayer audioPlayer)
{
_audioPlayer = audioPlayer;
}
public void Play(string audioFileLocation)
{
_audioPlayer.SetAudioFileLocation(audioFileLocation);
_audioPlayer.StartPlayAudio();
}
public void Stop()
{
_audioPlayer.StopPlayAudio();
}
}
The existing LegacyAudioPlayer
The LegacyAudioPlayer, which is a library provided from a vendor, handles mp3 files and its code is shown below.
public class LegacyAudioPlayer
{
private string _audioFileLocation;
public virtual void SetAudioFileLocation(string audioFileLocation)
{
_audioFileLocation = audioFileLocation;
}
public virtual void StartPlayAudio()
{
Console.WriteLine($"start playing mp3 audio file:{_audioFileLocation} from:{nameof(LegacyAudioPlayer)}");
}
public virtual void StopPlayAudio()
{
Console.WriteLine($"stop playing mp3 audio file:{_audioFileLocation} from:{nameof(LegacyAudioPlayer)}");
}
}
The incompatible FlacAudioPlayer
However, new file types must be supported, so we have to add support for FLAC files also. We could extend the LegacyAudioPlayer to make it support FLAC files, but the LegacyAudioPlayer is a vendor’s library and we do not have access to its code in order to alter it. After some research, we found from another vendor a new library, the FlacAudioPlayer, that can play FLAC files.
public class FlacAudioPlayer
{
private readonly int baudRate;
private readonly string audioFileLocation;
public FlacAudioPlayer(int baudRate, string audioFileLocation)
{
this.baudRate = baudRate;
this.audioFileLocation = audioFileLocation;
}
public void PlayFlac()
{
Console.WriteLine($"playing flac file:{this.audioFileLocation} at baudrate:{baudRate} from {nameof(FlacAudioPlayer)}");
}
public void Pause()
{
Console.WriteLine($"paused flac file:{this.audioFileLocation} from {nameof(FlacAudioPlayer)}");
}
}
The problem now is that the interface of the new FlacAudioPlayer is incompatible with the existing LegacyAudioPlayer that is used in our AudioPlayerClient. Given that we cannot change either the FlacAudioPlayer or LegacyAudioPlayer code, the only solution we can implement is to create an Adapter, that will translate the FlacAudioPlayer interface to the LegacyAudioPlayer interface. That way, the AudioPlayerClient can use both, without even knowing which one is using, and our application can support both mp3 and FLAC files.
Creating the FlacAudioPlayerAdapter
In order to translate the FlacAudioPlayer to another interface we create a FlacAudioPlayerAdapter that wraps (encapsulates) the incompatible FlacAudioPlayer and inherits from the LegacyAudioPlayer. That way, the FlacAudioPlayerAdapter and the LegacyAudioPlayer will have the same interface and would be interchanchable.
public class FlacAudioPlayerAdapter : LegacyAudioPlayer
{
FlacAudioPlayer flacAudioPlayer { get; set; }
public override void SetAudioFileLocation(string audioFileLocation)
{
this.flacAudioPlayer = new FlacAudioPlayer(9600, audioFileLocation);
}
public override void StartPlayAudio()
{
this.flacAudioPlayer.PlayFlac();
}
public override void StopPlayAudio()
{
this.flacAudioPlayer.Pause();
}
}
Usage example
Now that we have all components in place, lets see how we can use the AudioPlayerClient for playing mp3 and FLAC files.
AudioPlayerClient client = new AudioPlayerClient(new LegacyAudioPlayer());
client.Play("song.mp3");
client.Stop();
client = new AudioPlayerClient(new FlacAudioPlayerAdapter());
client.Play("song.flac");
client.Stop();
In the second case, we want to play a FLAC file, so we inject the FlacAudioPlayerAdapter to the AudioPlayerClient. The FlacAudioPlayerAdapter has the same interface with LegacyAudioPlayer and the AudioPlayerClient doesn’t know the difference.
Finally, the output is shown below.
start playing mp3 audio file:song.mp3 from:LegacyAudioPlayer
stop playing mp3 audio file:song.mp3 from:LegacyAudioPlayer
playing flac file:song.flac at baudrate:9600 from FlacAudioPlayer
paused flac file:song.flac from FlacAudioPlayer
Recommendations
If you want to learn more, refresh or better understand design patterns and software design in general, I also recommend reading the following books:
A must in Design Patterns
A very well written book with plenty of examples and insights