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
EasyLet'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 classAudioSourcethat your modern system expects - it should have a methodString playAudio()that returns the audio output as a string. Then create aVinylPlayerclass representing the legacy equipment - it has atrackNamefield and a methodString spinRecord()that returns*crackle* Playing vinyl: [trackName] *crackle*. Finally, build aVinylAdapterthat implementsAudioSourceand wraps aVinylPlayer. The adapter'splayAudio()method should call the vinyl player'sspinRecord()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 modernAudioSourceinterface. Create aVinylPlayerwith the track nameBohemian Rhapsody. Then create aVinylAdapterthat wraps this vinyl player. Store the adapter in a variable of typeAudioSourceto prove it conforms to the expected interface. CallplayAudio()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
}
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 FilesLibraries & ImportsIntroduction to OOPClasses vs ObjectsThe this KeywordMethodsInstance VariablesConstructor BasicsRecap - Simple Calculator4Null Safety
Intro to Null SafetyNullable vs Non-NullableThe ? and ! OperatorsLate Keyword & Null SafetyNull-Aware OperatorsNull Safety in ClassesRecap - User Profile System7Abstract Classes & Interfaces
Abstract ClassesAbstract MethodsInterfaces in DartImplicit InterfacesImplementing vs ExtendingMultiple InterfacesRecap - Shape Calculator10Collections & Generics
List, Set, Map OverviewType-Safe CollectionsGeneric ClassesGeneric MethodsGeneric ConstraintsIterable & IteratorRecap - Generic Storage13Advanced OOP Concepts
Composition vs InheritanceExtension MethodsCallable ClassesSealed Classes (Dart 3)Records (Dart 3)Patterns & Matching (3.0)Enums with Methods16Project: Library Management
Project OverviewBook and User Classes2Constructors in Dart
Default ConstructorNamed ConstructorsInitializer ListsConstant ConstructorsFactory ConstructorsRedirecting ConstructorsRecap - Shape Builder5Encapsulation
Public vs Private MembersThe _ Prefix ConventionLibrary-Level PrivacyGetters & Setters DepthInformation HidingRecap - Student Records8Mixins
Introduction to MixinsCreating MixinsUsing Multiple Mixinson Keyword in MixinsMixin vs InheritanceMixin vs InterfaceRecap - Animal System11Special Methods
toString() OverridehashCode & == OverrideComparable Interfacecall() MethodnoSuchMethod OverrideRecap - Custom Collection14Design Patterns Part 1
Intro to Design PatternsSingleton PatternFactory PatternObserver PatternStrategy Pattern3Class Properties
Instance vs Static MembersFinal & Const FieldsLate VariablesStatic Methods & FieldsGetters and SettersRecap - Bank Account Manager6Inheritance
Basic InheritanceThe super KeywordMethod OverridingThe @override AnnotationThe final Class KeywordConstructors & InheritanceRecap - Employee Hierarchy9Polymorphism
Polymorphism BasicsPolymorphism via InterfacesRuntime Type CheckingThe is & as OperatorsCovariant KeywordRecap - Payment Processor12Async OOP
Futures & async/awaitStreams BasicsStream ControllersAsync ConstructorsAsync in Class MethodsRecap - Data Fetcher15Design Patterns Part 2
Command PatternAdapter PatternDecorator PatternTemplate Method PatternState PatternComposite PatternRepository Pattern