Adapter and Facade Patterns – Head First Approach
Simplifying Complexity with Adapter and Facade Patterns
The Adapter Pattern and Facade Pattern are structural design patterns that deal with organizing relationships between objects. They help simplify and unify complex systems by making interfaces compatible (Adapter) or providing an easier interface to a system (Facade).
Adapter Pattern
What is the Adapter Pattern?
Definition: The Adapter Pattern allows objects with incompatible interfaces to work together by wrapping one object with an interface expected by the client. It acts as a bridge between two incompatible systems.
Problem Scenario
Imagine you are developing a duck simulator that uses a Duck
interface. You want to integrate a new type of bird, such as a turkey, into the simulator. However, turkeys have a different interface than ducks (e.g., they gobble instead of quacking and fly shorter distances). How can you integrate turkeys without modifying the existing code?
Using the Adapter Pattern
The Adapter Pattern allows us to wrap the turkey inside a duck adapter, making the turkey compatible with the duck interface.
Step 1: Define the Target Interface (Duck)
public interface Duck {
public void quack();
public void fly();
}
Step 2: Create the Adaptee (Turkey)
public interface Turkey {
public void gobble();
public void fly();
}
public class WildTurkey implements Turkey {
public void gobble() {
System.out.println("Gobble gobble");
}
public void fly() {
System.out.println("I'm flying a short distance");
}
}
Step 3: Implement the Adapter
The adapter implements the Duck
interface and wraps the Turkey
object, converting its behavior to match that of a Duck
.
public class TurkeyAdapter implements Duck {
Turkey turkey;
public TurkeyAdapter(Turkey turkey) {
this.turkey = turkey;
}
public void quack() {
turkey.gobble();
}
public void fly() {
for (int i = 0; i < 5; i++) {
turkey.fly();
}
}
}
Step 4: Client Code
The client uses the adapter to treat a turkey as if it were a duck.
public class DuckTestDrive {
public static void main(String[] args) {
Duck mallardDuck = new MallardDuck();
Turkey wildTurkey = new WildTurkey();
Duck turkeyAdapter = new TurkeyAdapter(wildTurkey);
System.out.println("The Turkey says...");
wildTurkey.gobble();
wildTurkey.fly();
System.out.println("\nThe Duck says...");
testDuck(mallardDuck);
System.out.println("\nThe TurkeyAdapter says...");
testDuck(turkeyAdapter);
}
static void testDuck(Duck duck) {
duck.quack();
duck.fly();
}
}
Output:
The Turkey says...
Gobble gobble
I'm flying a short distance
The Duck says...
Quack
I'm flying
The TurkeyAdapter says...
Gobble gobble
I'm flying a short distance
I'm flying a short distance
I'm flying a short distance
I'm flying a short distance
I'm flying a short distance
The TurkeyAdapter
converts turkey behavior into duck behavior, allowing the turkey to function within the duck simulator.
Facade Pattern
What is the Facade Pattern?
Definition: The Facade Pattern provides a simplified interface to a complex subsystem, making it easier for clients to interact with that subsystem. Instead of exposing all the subsystem’s functionality, the facade offers only the essential methods that clients need.
Problem Scenario
Imagine you’re developing a home theater system with several components, such as an amplifier, tuner, DVD player, and projector. Each component has its own interface and set of controls, making the system complex to operate. Users want an easier way to interact with the system.
Using the Facade Pattern
The Facade Pattern creates a simple interface that interacts with the home theater’s components behind the scenes, hiding the complexity from the user.
Step 1: Subsystem Components
Here are some of the components in the home theater system:
public class Amplifier {
public void on() {
System.out.println("Amplifier on");
}
public void off() {
System.out.println("Amplifier off");
}
public void setVolume(int level) {
System.out.println("Amplifier setting volume to " + level);
}
}
public class DVDPlayer {
public void on() {
System.out.println("DVD Player on");
}
public void off() {
System.out.println("DVD Player off");
}
public void play(String movie) {
System.out.println("Playing movie: " + movie);
}
public void stop() {
System.out.println("Stopping movie");
}
}
public class Projector {
public void on() {
System.out.println("Projector on");
}
public void off() {
System.out.println("Projector off");
}
public void wideScreenMode() {
System.out.println("Projector in widescreen mode");
}
}
Step 2: Implement the Facade
The facade simplifies the process of watching a movie by interacting with the underlying components.
public class HomeTheaterFacade {
Amplifier amp;
DVDPlayer dvd;
Projector projector;
public HomeTheaterFacade(Amplifier amp, DVDPlayer dvd, Projector projector) {
this.amp = amp;
this.dvd = dvd;
this.projector = projector;
}
public void watchMovie(String movie) {
System.out.println("Get ready to watch a movie...");
amp.on();
projector.on();
projector.wideScreenMode();
dvd.on();
dvd.play(movie);
amp.setVolume(10);
}
public void endMovie() {
System.out.println("Shutting movie theater down...");
amp.off();
projector.off();
dvd.stop();
dvd.off();
}
}
Step 3: Client Code
The client interacts with the home theater through the facade’s simplified interface.
public class HomeTheaterTestDrive {
public static void main(String[] args) {
Amplifier amp = new Amplifier();
DVDPlayer dvd = new DVDPlayer();
Projector projector = new Projector();
HomeTheaterFacade homeTheater = new HomeTheaterFacade(amp, dvd, projector);
homeTheater.watchMovie("Inception");
homeTheater.endMovie();
}
}
Output:
Get ready to watch a movie...
Amplifier on
Projector on
Projector in widescreen mode
DVD Player on
Playing movie: Inception
Amplifier setting volume to 10
Shutting movie theater down...
Amplifier off
Projector off
Stopping movie
DVD Player off
The HomeTheaterFacade
provides a clean, user-friendly interface to the complex system, hiding the details of how the components interact.
Key Differences Between Adapter and Facade Patterns
Adapter Pattern: Used to convert one interface into another to make two incompatible interfaces work together. It focuses on wrapping one object.
Facade Pattern: Used to provide a simplified interface to a complex subsystem. It focuses on simplifying interactions with multiple objects.
When to Use the Adapter Pattern
When you need to use an existing class but its interface is not compatible with the one you need.
When you want to integrate new systems into your application without changing existing code.
When to Use the Facade Pattern
When you want to provide a simple interface to a complex system.
When you want to decouple clients from complex subsystems, making the system easier to use or maintain.
Conclusion
The Adapter Pattern and Facade Pattern are invaluable tools for managing complex systems. The Adapter allows incompatible interfaces to work together, while the Facade simplifies the interaction with complex subsystems. Understanding when and how to use these patterns can help you design more flexible, maintainable software systems.