Adapter
The adapter pattern
The adapter pattern is a structural pattern and, simply put, is for two non-compatable interfaces and you either inherit both interface and class or you inherit the required interface and composite the class that's to be converted.An example is better than a description, therefore in C#, I've invented a convoluted system which is a MediaPlayer.
The MediaPlayer needs a decoder for the different files it has to play (eg mp3, aiff, wav...) and expects both as arguments.
We happily write the decoders for each media file, by implementing the interface IMediaPlayer (below):
interface IMediaPlayer {
void play(string filename, IDecoder decoder); // ignore the void
}
void play(string filename, IDecoder decoder); // ignore the void
}
This has only one method play which returns nothing, which we'll ignore, as it should be returning a stream of decoded data.
Examples of implementations:
class Mp3Decoder : IDecoder {
public void Decode(string filename) {
Console.WriteLine("'Decoding {0}'.", filename);
// decode something
}
public override string ToString() => "Mp3Decoder";
}
public void Decode(string filename) {
Console.WriteLine("'Decoding {0}'.", filename);
// decode something
}
public override string ToString() => "Mp3Decoder";
}
Another simple implementation:
class AIFFDecoder : IDecoder {
public void Decode(string filename) {
Console.WriteLine("'Decoding {0}'.", filename);
// decode something
}
public override string ToString() => "AIFFDecoder";
}
public void Decode(string filename) {
Console.WriteLine("'Decoding {0}'.", filename);
// decode something
}
public override string ToString() => "AIFFDecoder";
}
We then need to use propriety decoders and we have no access to the base code so we 'find' the decoders (and ignoring any cohesive issues) we write adapters for these.
For example, the underlying WAV decoder is called WavPlayer, we only know it has a method Change, ie:
class WavPlayer : IWavPlayer { // adaptee
// this is the equivalent Decode
public void Change(string filename) {
Console.WriteLine("'Decoding {0}'.", filename);
}
// ... lots of other stuff ...
public override string ToString() => "WavDecoder";
}
// this is the equivalent Decode
public void Change(string filename) {
Console.WriteLine("'Decoding {0}'.", filename);
}
// ... lots of other stuff ...
public override string ToString() => "WavDecoder";
}
Interestingly, if the adaptee has been written with maximum cohesion then we can have two varieties of adapter, one inherits and other composites but both implement the interface IMediaPlayer.
The inherited version:
class WavDecoderAdapterInherit : WavPlayer, IDecoder {
public void Decode(string filename) {
Change(filename);
}
public override string ToString() => "WavDecoderAdapterInherit";
}
public void Decode(string filename) {
Change(filename);
}
public override string ToString() => "WavDecoderAdapterInherit";
}
Or we can use the composited version:
class WavDecoderAdapter : IDecoder {
public void Decode(string filename) {
// if the WavPlayer changes then this will have to be changed
IWavPlayer wavPlayer = new WavPlayer();
wavPlayer.Change(filename);
}
public override string ToString() => "WavDecoderAdapter";
}
public void Decode(string filename) {
// if the WavPlayer changes then this will have to be changed
IWavPlayer wavPlayer = new WavPlayer();
wavPlayer.Change(filename);
}
public override string ToString() => "WavDecoderAdapter";
}
Just for completeness, here's the test class that shows the output:
class Program {
static void Main(string[] args) {
IMediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.play("The only way is up", new Mp3Decoder());
mediaPlayer.play("Happy as I know it", new AIFFDecoder());
mediaPlayer.play("Paint it black", new WavPackDecoder());
mediaPlayer.play("Blue Monday", new WavDecoderAdapter());
mediaPlayer.play("Manic Monday", new WavDecoderAdapterInherit());
Console.WriteLine("Press any key to finish...");
Console.ReadKey();
}
}
static void Main(string[] args) {
IMediaPlayer mediaPlayer = new MediaPlayer();
mediaPlayer.play("The only way is up", new Mp3Decoder());
mediaPlayer.play("Happy as I know it", new AIFFDecoder());
mediaPlayer.play("Paint it black", new WavPackDecoder());
mediaPlayer.play("Blue Monday", new WavDecoderAdapter());
mediaPlayer.play("Manic Monday", new WavDecoderAdapterInherit());
Console.WriteLine("Press any key to finish...");
Console.ReadKey();
}
}
The output looks like this:
'Decoding The only way is up'.
Playing 'The only way is up' with decoder Mp3Decoder
'Decoding Happy as I know it'.
Playing 'Happy as I know it' with decoder AIFFDecoder
'Decoding Paint it black'.
Playing 'Paint it black' with decoder WavPackDecoder
'Decoding Blue Monday'.
Playing 'Blue Monday' with decoder WavDecoderAdapter
'Decoding Manic Monday'.
Playing 'Manic Monday' with decoder WavDecoderAdapterInherit
Press any key to finish...
Playing 'The only way is up' with decoder Mp3Decoder
'Decoding Happy as I know it'.
Playing 'Happy as I know it' with decoder AIFFDecoder
'Decoding Paint it black'.
Playing 'Paint it black' with decoder WavPackDecoder
'Decoding Blue Monday'.
Playing 'Blue Monday' with decoder WavDecoderAdapter
'Decoding Manic Monday'.
Playing 'Manic Monday' with decoder WavDecoderAdapterInherit
Press any key to finish...
Summary
There we have it, the Adapter pattern which is pretty simple, or is it?
The example I've given is very simple and was built with the adapter pattern in mind.
On my initial attempts and perhaps because of weak cohesion,
I had some difficulties, especially with the inherited version, the pattern didn't work as the function (method)
that's called has a signature (arguments etc...) and if the signature wasn't cohesive enough then it wasn't possible to form the
adapter.
The full listing for Visual Studio is here.