Structural patterns are concerned with how classes and objects are composed to form larger structures. They use inheritance and composition to create new functionality from existing components.
Intent: Convert the interface of a class into another interface clients expect. Adapter lets classes work together that couldn't otherwise because of incompatible interfaces.
When you travel from India to Europe, your Indian plug doesn't fit into a European wall socket. You use a Power Adapter that sits between your plug and the socket, converting one interface to another without modifying either the plug or the socket.
You are building a stock market analytics app. Your app uses a beautiful charting library that only accepts data in XML format. But your new data vendor's API only returns data in JSON format. You cannot modify the vendor's API or the charting library.
// The interface your charting library expects
interface XMLDataSource {
String getXMLData();
}
// The vendor's class you CANNOT modify
class JsonDataVendor {
public String getJsonData() {
return "{ \"price\": 150.25 }";
}
}
// The ADAPTER: Bridges the gap
class JsonToXmlAdapter implements XMLDataSource {
private JsonDataVendor vendor;
JsonToXmlAdapter(JsonDataVendor vendor) {
this.vendor = vendor;
}
public String getXMLData() {
String json = vendor.getJsonData();
// Convert JSON to XML
return "<price>150.25</price>";
}
}
The charting library works with XMLDataSource. The adapter implements XMLDataSource but internally delegates to JsonDataVendor, performing the conversion transparently.
Intent: Attach additional responsibilities to an object dynamically. Decorators provide a flexible alternative to subclassing for extending functionality.
A plain coffee costs $2. You want to add Milk (+$0.50), then Whipped Cream (+$0.75), then Caramel Drizzle (+$1.00). Each addition "decorates" the base coffee, adding cost and description.
If you used inheritance, you would need classes like CoffeeWithMilk, CoffeeWithMilkAndWhippedCream, CoffeeWithCaramelAndWhippedCream... The number of subclasses explodes combinatorially.
interface Coffee {
String getDescription();
double getCost();
}
class BasicCoffee implements Coffee {
public String getDescription() { return "Basic Coffee"; }
public double getCost() { return 2.00; }
}
// Abstract Decorator
abstract class CoffeeDecorator implements Coffee {
protected Coffee decoratedCoffee;
CoffeeDecorator(Coffee coffee) { this.decoratedCoffee = coffee; }
}
// Concrete Decorators
class MilkDecorator extends CoffeeDecorator {
MilkDecorator(Coffee coffee) { super(coffee); }
public String getDescription() { return decoratedCoffee.getDescription() + " + Milk"; }
public double getCost() { return decoratedCoffee.getCost() + 0.50; }
}
class WhipDecorator extends CoffeeDecorator {
WhipDecorator(Coffee coffee) { super(coffee); }
public String getDescription() { return decoratedCoffee.getDescription() + " + Whip"; }
public double getCost() { return decoratedCoffee.getCost() + 0.75; }
}
// Usage: Decorators wrap objects like layers
Coffee order = new BasicCoffee(); // $2.00
order = new MilkDecorator(order); // $2.50
order = new WhipDecorator(order); // $3.25
System.out.println(order.getDescription()); // "Basic Coffee + Milk + Whip"
System.out.println(order.getCost()); // 3.25
Each decorator wraps the previous object, adding its own behavior. You can stack decorators in any combination without creating a single new subclass.