When to use adapter design pattern in Java?

Sunny 70 Published: 10/21/2024

When to use adapter design pattern in Java?

I'd love to help you with that!

The Adapter Design Pattern is a structural pattern that allows two incompatible objects to collaborate by converting the interface of one object into an interface expected by the other object. In other words, it acts as a bridge between two classes that have different interfaces.

To use the adapter design pattern in Java, follow these guidelines:

Identify the problem: Determine when you need to integrate two classes with different interfaces or behaviors. This might happen when: You're working with legacy code and need to interface with a new class. You want to reuse an existing class without modifying its internal structure. Two systems need to communicate, but they use different data formats or protocols. Create the adapter: Develop an Adapter class that implements the target interface (the one expected by the other object). This class will convert the requests and responses between the two original objects.

Here's a simple example in Java:

// Target Interface

interface Animal {

void sound();

}

// Class 1 with different interface

class Dog {

public void bark() {

System.out.println("Woof!");

}

}

// Adapter for Class 1

class DogAdapter implements Animal {

private Dog dog;

public DogAdapter(Dog dog) {

this.dog = dog;

}

@Override

public void sound() {

dog.bark();

}

}

// Client Code

public class Main {

public static void main(String[] args) {

Dog myDog = new Dog();

Animal animal = new DogAdapter(myDog);

// Now you can use the Dog with the Animal interface:

animal.sound(); // prints: Woof!

}

}

In this example, the Dog class has a different interface than the target interface Animal. The DogAdapter acts as a bridge between them, allowing you to call sound() on an instance of Dog and have it execute the original bark() method.

Benefits: By using the adapter design pattern in Java: You can integrate classes with incompatible interfaces. You avoid modifying the internal structure of existing code. You promote code reuse by allowing different systems to communicate.

When not to use the adapter design pattern:

When you need to modify the internal structure of one or both classes (in that case, consider refactoring). When there are no compatibility issues between the two objects (just use polymorphism).

By applying the adapter design pattern thoughtfully, you'll be able to connect seemingly disparate classes and make your code more flexible, adaptable, and maintainable.

Decorator design pattern in Java

The decorator design pattern is a structural design pattern that allows you to dynamically add additional responsibilities to an object without affecting its external interface.

In Java, the decorator design pattern can be implemented using interfaces and classes. The main components of this pattern are:

Component: This is the base class or interface that defines the common interface for both the concrete component and the decorators. Concrete Component: This is the specific implementation of the Component interface. It's the class that we want to decorate. Decorator: This is an abstract class that implements the Component interface. The decorator has a reference to a Component object, which it delegates calls to by default.

Here is an example of how to implement this pattern in Java:

// Component interface

interface Coffee {

String getDescription();

int getCost();

}

// Concrete Component: SimpleCoffee

class SimpleCoffee implements Coffee {

@Override

public String getDescription() {

return "Simple Coffee";

}

@Override

public int getCost() {

return 10;

}

}

// Decorator: CondimentDecorator

abstract class CondimentDecorator implements Coffee {

protected Coffee coffee;

public CondimentDecorator(Coffee coffee) {

this.coffee = coffee;

}

@Override

public String getDescription() {

return coffee.getDescription();

}

@Override

public int getCost() {

return coffee.getCost();

}

}

// Concrete Decorator: Milk

class Milk extends CondimentDecorator {

public Milk(Coffee coffee) {

super(coffee);

}

@Override

public String getDescription() {

return super.getDescription() + ", Milk";

}

@Override

public int getCost() {

return super.getCost() + 10;

}

}

// Concrete Decorator: Sugar

class Sugar extends CondimentDecorator {

public Sugar(Coffee coffee) {

super(coffee);

}

@Override

public String getDescription() {

return super.getDescription() + ", Sugar";

}

@Override

public int getCost() {

return super.getCost() + 5;

}

}

// Client code

public class Main {

public static void main(String[] args) {

// Simple Coffee

Coffee coffee = new SimpleCoffee();

System.out.println("Description: " + coffee.getDescription());

System.out.println("Cost: " + coffee.getCost());

// Adding Milk to the coffee

coffee = new Milk(coffee);

System.out.println("Description: " + coffee.getDescription());

System.out.println("Cost: " + coffee.getCost());

// Adding Sugar to the coffee

coffee = new Sugar(coffee);

System.out.println("Description: " + coffee.getDescription());

System.out.println("Cost: " + coffee.getCost());

}

}

This example shows how you can use the decorator pattern to add different condiments (like Milk or Sugar) to a simple coffee. Each condiment is an instance of the CondimentDecorator class, which wraps around another coffee object. The client code (in this case, the Main class) can dynamically add these condiments to the coffee, and it doesn't need to know about the specific implementation details of each condiment.