Menu
Coddy logo textTech

Adapter Pattern

Part of the Object Oriented Programming section of Coddy's Dart journey — lesson 95 of 110.

The Adapter pattern allows incompatible interfaces to work together. It acts as a bridge between two classes that couldn't otherwise collaborate, wrapping an existing class with a new interface that clients expect. This is particularly useful when integrating third-party libraries or legacy code into your application.

Imagine you have a system that works with JSON data, but you need to integrate a component that only outputs XML. Instead of modifying either class, you create an adapter that translates between them:

// Existing interface your system expects
abstract class JsonData {
  String getJson();
}

// Legacy class with incompatible interface
class XmlService {
  String getXml() {
    return '<data><name>John</name></data>';
  }
}

// Adapter makes XmlService compatible with JsonData
class XmlToJsonAdapter implements JsonData {
  final XmlService _xmlService;

  XmlToJsonAdapter(this._xmlService);

  @override
  String getJson() {
    String xml = _xmlService.getXml();
    // Convert XML to JSON (simplified)
    return '{"name": "John"}';
  }
}

void main() {
  var xmlService = XmlService();
  var adapter = XmlToJsonAdapter(xmlService);

  print(adapter.getJson());  // {"name": "John"}
}

The XmlToJsonAdapter wraps the XmlService and implements the JsonData interface. Client code works with the familiar JsonData interface without knowing that the data originally came from an XML source. The adapter handles all the translation internally.

Use the Adapter pattern when you want to use an existing class but its interface doesn't match what you need, or when you need to create a reusable class that cooperates with classes having incompatible interfaces.

challenge icon

Challenge

Easy

Let's build a media player adapter system! Imagine you have a modern audio system that expects all media sources to provide audio through a standard interface, but you need to integrate an old-school vinyl record player that has a completely different way of producing sound.

You'll create two files to organize your adapter pattern implementation:

  • media_adapter.dart: This file contains your interface and adapter classes. Define an abstract class AudioSource that your modern system expects - it should have a method String playAudio() that returns the audio output as a string. Then create a VinylPlayer class representing the legacy equipment - it has a trackName field and a method String spinRecord() that returns *crackle* Playing vinyl: [trackName] *crackle*. Finally, build a VinylAdapter that implements AudioSource and wraps a VinylPlayer. The adapter's playAudio() method should call the vinyl player's spinRecord() method and return the result - bridging the incompatible interfaces.
  • main.dart: Import your media adapter file and demonstrate how the adapter makes the old vinyl player work with code that expects the modern AudioSource interface. Create a VinylPlayer with the track name Bohemian Rhapsody. Then create a VinylAdapter that wraps this vinyl player. Store the adapter in a variable of type AudioSource to prove it conforms to the expected interface. Call playAudio() on your audio source and print the result.

The beauty of this pattern is that your main code works with the standard AudioSource interface - it doesn't need to know that behind the scenes, an old vinyl player is doing the actual work!

Expected output:

*crackle* Playing vinyl: Bohemian Rhapsody *crackle*

Try it yourself

import 'media_adapter.dart';

void main() {
  // TODO: Create a VinylPlayer with the track name "Bohemian Rhapsody"
  
  // TODO: Create a VinylAdapter that wraps the vinyl player
  
  // TODO: Store the adapter in a variable of type AudioSource
  
  // TODO: Call playAudio() on your audio source and print the result
  
}
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