Adapter Pattern
Part of the Object Oriented Programming section of Coddy's Java journey — lesson 71 of 87.
The Adapter Pattern is a structural design pattern that allows incompatible interfaces to work together. It acts as a bridge between two classes, converting the interface of one class into an interface that the client expects. Think of it like a power adapter that lets you plug a European device into an American outlet.
Imagine you have an existing MediaPlayer interface that plays audio files, but you need to integrate a third-party library that uses a completely different interface for playing videos:
// Existing interface your code uses
interface MediaPlayer {
void play(String filename);
}
// Third-party class with incompatible interface
class AdvancedVideoPlayer {
public void playVideo(String filename) {
System.out.println("Playing video: " + filename);
}
}The adapter wraps the incompatible class and implements the expected interface, translating calls between them:
class VideoPlayerAdapter implements MediaPlayer {
private AdvancedVideoPlayer videoPlayer;
public VideoPlayerAdapter() {
this.videoPlayer = new AdvancedVideoPlayer();
}
public void play(String filename) {
videoPlayer.playVideo(filename);
}
}Now your client code can use the video player through the familiar MediaPlayer interface:
MediaPlayer player = new VideoPlayerAdapter();
player.play("movie.mp4"); // Output: Playing video: movie.mp4The Adapter Pattern is invaluable when integrating legacy code or third-party libraries without modifying their source code. The client remains unaware that it's working with an adapted class—it simply uses the interface it knows.
Challenge
EasyLet's build a temperature conversion system using the Adapter Pattern! You have an existing weather application that works with Celsius temperatures, but you need to integrate a legacy temperature sensor that only outputs readings in Fahrenheit. Instead of modifying either system, you'll create an adapter that bridges the gap.
You'll organize your code across four files:
TemperatureProvider.java: Define the interface that your weather application expects. It should declare a methodgetTemperatureCelsius()that returns a double representing the temperature in Celsius.FahrenheitSensor.java: This represents the legacy sensor with an incompatible interface. Create a class with a private double field for the temperature in Fahrenheit, set through the constructor. It should have a methodreadFahrenheit()that returns the stored Fahrenheit value.SensorAdapter.java: Create the adapter that makes the Fahrenheit sensor compatible with your weather application. Your adapter should implementTemperatureProviderand wrap aFahrenheitSensor. WhengetTemperatureCelsius()is called, it should read from the sensor and convert the value to Celsius using the formula:(fahrenheit - 32) * 5 / 9.Main.java: Bring everything together! You'll receive one input: a temperature reading in Fahrenheit (double).Create a
FahrenheitSensorwith the input value. Then create aSensorAdapterthat wraps this sensor. Store the adapter in aTemperatureProvidervariable to demonstrate that your weather application can use it through the expected interface.Print the result in this format:
Temperature: [celsius] Cwhere celsius is formatted to one decimal place.
You will receive one input: the Fahrenheit temperature (double).
For example, with input 98.6, your output would be:
Temperature: 37.0 CNotice how your Main class works entirely with the TemperatureProvider interface—it has no idea that the actual temperature is coming from a Fahrenheit sensor being adapted behind the scenes. This is the elegance of the Adapter Pattern!
Cheat sheet
The Adapter Pattern is a structural design pattern that allows incompatible interfaces to work together by acting as a bridge between two classes, converting one interface into another that the client expects.
Key Components
- Target Interface: The interface that the client expects to use
- Adaptee: The existing class with an incompatible interface
- Adapter: The class that implements the target interface and wraps the adaptee, translating calls between them
Basic Structure
// Target interface
interface MediaPlayer {
void play(String filename);
}
// Adaptee (incompatible class)
class AdvancedVideoPlayer {
public void playVideo(String filename) {
System.out.println("Playing video: " + filename);
}
}
// Adapter
class VideoPlayerAdapter implements MediaPlayer {
private AdvancedVideoPlayer videoPlayer;
public VideoPlayerAdapter() {
this.videoPlayer = new AdvancedVideoPlayer();
}
public void play(String filename) {
videoPlayer.playVideo(filename);
}
}
// Client usage
MediaPlayer player = new VideoPlayerAdapter();
player.play("movie.mp4"); // Output: Playing video: movie.mp4When to Use
The Adapter Pattern is useful when:
- Integrating legacy code or third-party libraries without modifying their source code
- Making incompatible interfaces work together
- The client needs to remain unaware of the adapted implementation
Try it yourself
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
double fahrenheit = scanner.nextDouble();
// TODO: Create a FahrenheitSensor with the input value
// TODO: Create a SensorAdapter that wraps the sensor
// TODO: Store the adapter in a TemperatureProvider variable
// TODO: Get the temperature in Celsius and print it
// Format: "Temperature: [celsius] C" with one decimal place
// Hint: Use String.format("%.1f", value) for formatting
}
}
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 FilesIntroduction to OOPClasses vs ObjectsThe this KeywordMethodsFields (Attributes)Constructor MethodConstructor OverloadingRecap - Simple Calculator4Inheritance
Basic Inheritance (extends)The super KeywordMethod Overriding (@Override)Constructor ChainingThe Object ClassSingle & Multilevel InheritWhy No Multi Class InheritRecap - Employee Hierarchy7Special Methods & Object Class
toString() Methodequals() and hashCode()clone() MethodcompareTo() and ComparableComparator InterfaceRecap - Custom Sorting2Access Modifiers & Encapsulate
Access Levels OverviewGetter and Setter MethodsInformation HidingThe final KeywordRecap - Bank Account Manager5Polymorphism
Method Overloading BasicsMethod Overriding (Run-Time)Upcasting and DowncastingThe instanceof OperatorAbstract Classes and MethodsRecap - Shape Calculator8Advanced OOP Concepts
Composition vs InheritanceAggregation vs CompositionInner Nested & Anonymous ClassEnums and Enum MethodsRecords (Java 16+)Sealed Classes (Java 17+)3Class Props & Static Member
Instance vs Static VariablesStatic MethodsStatic BlocksConstants (static final)Recap - Counter & Utility6Interfaces & Abstract Classes
Introduction to InterfacesImplementing InterfacesMulti Interface ImplemenDefault & Static in InterfaceAbstract Classes vs InterfacesFunctional InterfacesRecap - Payment System9Generics
Introduction to GenericsGeneric ClassesGeneric MethodsBounded Type ParametersWildcards (?, extends, super)Recap - Generic Container12Design Patterns Part 2
Command PatternAdapter PatternDecorator PatternTemplate Method PatternState PatternComposite PatternIterator Pattern