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*

Cheat sheet

The Adapter pattern allows incompatible interfaces to work together by acting as a bridge between two classes that couldn't otherwise collaborate.

The adapter wraps an existing class with a new interface that clients expect, making it useful when integrating third-party libraries or legacy code.

Basic Structure

// Target interface that clients expect
abstract class JsonData {
  String getJson();
}

// Existing 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
    return '{"name": "John"}';
  }
}

Usage

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

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

The adapter implements the target interface while wrapping the incompatible class. Client code works with the familiar interface without knowing about the underlying implementation.

When to use: When you need to use an existing class but its interface doesn't match what you need, or when creating a reusable class that cooperates with classes having incompatible interfaces.

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