Java
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented Java
-
Section 4: Collections Framework
-
Section 5: Exception Handling
-
Section 6: Generics
-
Section 7: Functional Java
-
Section 8: Concurrency
-
Section 9: I/O and NIO
-
Section 10: JVM Internals
-
Section 11: Modern Java Features
-
Section 12: Build Tools and Project Structure
-
Section 13: Testing
-
Section 14: Databases and Persistence
-
Section 15: Networking
-
Section 16: Design and Best Practices
-
Section 17: Reflection and Annotations
-
Section 18: Logging and Diagnostics
-
Section 19: Date, Time, and Internationalization
-
Section 20: Java Platform Module System
-
Section 21: Security in Java
-
Section 22: Advanced Collections and Data Structures
-
Section 23: More Concurrency Patterns
-
Section 24: Compression, Files, and System Integration
-
Section 25: GUI Programming
-
Section 26: Practical Projects
-
Section 27: Data Structures and Algorithms
-
Section 28: Interview and Algorithm Practice
-
Section 29: JSON and Data Interchange
-
Section 30: More Concurrency Utilities
-
Section 31: More Collections and Streams Practice
-
Section 32: More File and System Programming
-
Section 33: Standard Library Deep Dive
-
Section 34: More Practice and Drills
-
Section 35: More Testing and Quality
-
Section 36: More Design Patterns and Architecture
-
Section 37: Career and Ecosystem
-
Section 38: More OOP and Architecture Practice
-
Section 39: More Enterprise Concepts
-
Section 40: Advanced JavaFX
-
Section 41: More Interview Practice
153: The Adapter Pattern
When do I actually need an adapter instead of just rewriting the code?
I get this question a lot. If the code is "wrong" or incompatible, why not just fix it? In a perfect world, you would. But in the real world, you're often dealing with code you cannot change. Maybe it's a third-party library you bought from a vendor, a legacy system that's too fragile to touch, or a shared module used by ten other teams who would all scream if you changed a method signature.
The Adapter pattern is your "diplomat." It allows two incompatible interfaces to talk to each other without forcing either one to change. You aren't fixing the legacy code; you're building a bridge. If you find yourself thinking, "I wish this class had a method that looked like that," you're looking at a prime candidate for an adapter.
How do I actually structure the adapter in Java?
Let's look at a concrete scenario. Suppose your app uses a standard PaymentProcessor interface. You've just integrated a new, fancy vendor called "QuickPay," but their SDK is written in a way that doesn't match your interface at all. Their method is called sendMoney(), while your system expects processPayment().
// This is what your system expects interface PaymentProcessor { void processPayment(double amount); } // This is the third-party class you can't change class QuickPaySDK { public void sendMoney(double value, String currency) { System.out.println("Sending " + value + " " + currency + " via QuickPay."); } }To make
QuickPaySDKwork with yourPaymentProcessor, you create an adapter class. This class implements your interface and wraps the SDK object:class QuickPayAdapter implements PaymentProcessor { private final QuickPaySDK quickPaySDK; public QuickPayAdapter(QuickPaySDK sdk) { this.quickPaySDK = sdk; } @Override public void processPayment(double amount) { // The adapter translates the call and fills in the gaps // (like the currency, which our interface doesn't track) quickPaySDK.sendMoney(amount, "USD"); } }Now, your main application logic doesn't even know
QuickPaySDKexists. It just sees aPaymentProcessorand callsprocessPayment(). The adapter handles the translation behind the scenes.Should I use inheritance or composition for the adapter?
You'll see two versions of this pattern in some textbooks: the "Class Adapter" (using inheritance) and the "Object Adapter" (using composition). I almost always recommend composition—which is what I used in the example above.
- Inheritance: You extend both the adapter and the adaptee. This is restrictive in Java because we don't have multiple inheritance. If your legacy class already extends something else, you're stuck.
- Composition: You hold a reference to the legacy object. This is way more flexible. You can swap out the adaptee at runtime, and it keeps your adapter class decoupled from the legacy implementation details.
Stick with composition. It's cleaner, it's safer, and it follows the "favor composition over inheritance" rule that will save you a lot of headaches as your project grows.
📋 Practical Task
Integrating a Legacy Temperature Sensor
You are building a climate control system that expects all sensors to implement a TemperatureSensor interface with a method double getTemperatureCelsius().
You've been handed a legacy sensor class called OldSchoolThermometer. This class is a compiled JAR and cannot be modified. It has one method: double getFahrenheit().
Your Task:
- Create the
TemperatureSensorinterface. - Create a mock
OldSchoolThermometerclass that returns a hardcoded Fahrenheit value (e.g., 98.6). - Implement an
OldSchoolAdapterthat implementsTemperatureSensorand wraps theOldSchoolThermometer. - In the adapter, implement the conversion logic:
Celsius = (Fahrenheit - 32) * 5/9. - Write a
Mainclass to demonstrate that you can pass the adapter into a method that expects aTemperatureSensorand receive the correct Celsius value.
There are no comments for now.