Adapter and Facade Patterns – Head First Approach

Alyaa TalaatAlyaa Talaat
5 min read

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.


0
Subscribe to my newsletter

Read articles from Alyaa Talaat directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Alyaa Talaat
Alyaa Talaat

As a continuous learner, I’m always exploring new technologies and best practices to enhance my skills in software development. I enjoy tackling complex coding challenges, whether it's optimizing performance, implementing new features, or debugging intricate issues.