Software Design & Architecture · Structural Patterns
Three patterns, one job in common: reshape how existing classes and objects fit together — without touching what's already there.
Structural design patterns deal with how classes and objects are composed to form larger structures — at run time. This chapter covers three of the most popular ones.
Makes two incompatible interfaces work together — like a power plug adapter.
Lets you treat a single object and a group of objects the same way — a tree of parts and wholes.
Wraps a complicated subsystem behind one simple, friendly interface.
Converts the interface of a class into another interface clients expect — so classes that couldn't otherwise work together, because of incompatible interfaces, now can.
| Aspect | Object Adapter | Class Adapter |
|---|---|---|
| Mechanism | Delegates to an Adaptee instance at run-time (composition) | Inherits from the Adaptee at compile-time |
| Requires | Just holding a reference to the adaptee object | Multiple inheritance — not supported by many languages |
| Preferred? | ✅ Yes — generally preferred | Rarely — only where multiple inheritance is available & safe |
Setup: a Bird class has fly() and
makeSound(). A ToyDuck interface only has squeak(). You're
short on real ToyDuck objects and want to use a Bird (Sparrow) in its place. Target = ToyDuck, Adaptee =
Bird.
interface ToyDuck { public void squeak(); } // Target interface Bird { public void fly(); public void makeSound(); } // Adaptee interface class Sparrow implements Bird { public void fly(){ System.out.println("Flying"); } public void makeSound(){ System.out.println("Chirp Chirp"); } } // The Adapter — implements Target, wraps an Adaptee class BirdAdapter implements ToyDuck { Bird bird; // reference to the object we're adapting public BirdAdapter(Bird bird) { this.bird = bird; } public void squeak() { bird.makeSound(); // translate the call to the adaptee's method } } // Client Sparrow sparrow = new Sparrow(); ToyDuck birdAdapter = new BirdAdapter(sparrow); // wrap it birdAdapter.squeak(); // → "Chirp Chirp" — a bird behaving like a toy duck!
Helps achieve reusability & flexibility. Client isn't complicated by a different interface, and can use polymorphism to swap between different adapter implementations.
All requests are forwarded → slight overhead. Sometimes several adaptations are needed in a chain to reach the required type.
Composes objects into tree structures to represent whole-part hierarchies. Lets clients treat individual objects and compositions of objects uniformly.
Both Leaf (a single object) and Composite (a collection of Components) implement the same Component interface — that's the whole trick.
A file is a primitive (Leaf) — it holds no other objects. A folder is a Composite — it can contain files and other folders.
Opening either uses the same open() operation.
A single course ("Math 101") is a Leaf. A program ("CS degree") is a Composite of many courses. Checking details works the same way for both.
public interface Component { public void sayHello(); public void sayGoodbye(); } public class Leaf implements Component { String name; public Leaf(String name){ this.name = name; } public void sayHello(){ System.out.println(name + " leaf says hello"); } public void sayGoodbye(){ System.out.println(name + " leaf says goodbye"); } } public class Composite implements Component { List<Component> components = new ArrayList<>(); public void add(Component c){ components.add(c); } public void sayHello(){ for(Component c : components) c.sayHello(); } public void sayGoodbye(){ for(Component c : components) c.sayGoodbye(); } } // Client Composite composite1 = new Composite(); composite1.add(new Leaf("Bob")); composite1.add(new Leaf("Fred")); composite1.sayHello(); // → "Bob leaf says hello" then "Fred leaf says hello" // composites can even nest inside other composites!
Provides a unified, simplified interface to a set of interfaces in a subsystem — a higher-level interface that makes the whole subsystem easier to use.
The client never touches ClassA / ClassB directly — only the Facade.
Setup: a MobileShop interface is implemented by Iphone,
Samsung, Blackberry. Instead of the client juggling all three directly, a ShopKeeper
Facade wraps them.
public interface MobileShop { void modelNo(); void price(); } public class Iphone implements MobileShop { /* Iphone 6, Rs 65000 */ } public class Samsung implements MobileShop { /* Galaxy Tab 3, Rs 45000 */ } public class Blackberry implements MobileShop { /* Z10, Rs 55000 */ } // The Facade public class ShopKeeper { private MobileShop iphone, samsung, blackberry; public ShopKeeper(){ iphone = new Iphone(); samsung = new Samsung(); blackberry = new Blackberry(); } public void iphoneSale(){ iphone.modelNo(); iphone.price(); } public void samsungSale(){ samsung.modelNo(); samsung.price(); } public void blackberrySale(){ blackberry.modelNo(); blackberry.price(); } } // Client — never touches Iphone/Samsung/Blackberry directly ShopKeeper sk = new ShopKeeper(); sk.iphoneSale(); // → "Iphone 6" · "Rs 65000.00"
A quick way to tell them apart on an exam: ask "what is this pattern actually solving?"
| Pattern | Solves | Shape | Cue phrase |
|---|---|---|---|
| Adapter | Two incompatible interfaces need to work together | Client → Adapter → Adaptee | "convert this interface into that one" |
| Composite | Need to treat single objects & groups of objects the same way | Tree: Component ← Leaf / Composite | "whole-part hierarchy, uniform treatment" |
| Facade | A subsystem is too complex for clients to use directly | Client → Facade → {ClassA, ClassB, …} | "one simple interface for a messy subsystem" |
Tap a card to flip it.
Written from this chapter's content — self-check practice, not an official past paper.