Fundamentals 53 min read

Master Design Patterns: From Proxy to Composite – A Complete Guide

This article explores the fundamentals of structural design patterns in software engineering, detailing the definitions, advantages, drawbacks, and implementations of patterns such as Proxy, Adapter, Bridge, Decorator, Facade, Flyweight, and Composite, complete with Java code examples, UML diagrams, and real‑world application scenarios.

Intelligent Backend & Architecture
Intelligent Backend & Architecture
Intelligent Backend & Architecture
Master Design Patterns: From Proxy to Composite – A Complete Guide

Structural Design Patterns

Structural design patterns focus on how classes and objects are composed to form larger structures. They are divided into class‑structure patterns (using inheritance) and object‑structure patterns (using composition or aggregation). Object‑structure patterns are generally more flexible because they reduce coupling and follow the Composite‑Reuse Principle.

1. Proxy Pattern

Definition: Provides a surrogate or placeholder for another object to control access to it.

Advantages:

Remote proxy hides the details of remote communication.

Virtual proxy can delay expensive object creation.

Protection proxy adds a security layer.

Disadvantages:

Introduces an extra level of indirection.

May affect performance.

Structure:

Key Roles:

Subject – declares the common interface.

RealSubject – the actual object that does the work.

Proxy – implements Subject and holds a reference to RealSubject.

Sample Code (Java):

public interface Subject {
    void request();
}

public class RealSubject implements Subject {
    public void request() {
        System.out.println("RealSubject handling request");
    }
}

public class Proxy implements Subject {
    private RealSubject real;
    public Proxy() { real = new RealSubject(); }
    public void request() {
        System.out.println("Proxy preprocessing");
        real.request();
        System.out.println("Proxy postprocessing");
    }
}

public class ProxyDemo {
    public static void main(String[] args) {
        Subject p = new Proxy();
        p.request();
    }
}

2. Adapter Pattern

Definition: Converts the interface of a class into another interface clients expect.

Advantages:

Allows reuse of existing classes without modification.

Promotes decoupling between client and adaptee.

Disadvantages:

Can increase system complexity.

Structure:

Key Roles:

Target – the desired interface.

Adaptee – the existing incompatible interface.

Adapter – implements Target and translates calls to Adaptee.

Sample Code (Java):

public interface Target {
    void request();
}

public class Adaptee {
    public void specificRequest() {
        System.out.println("Adaptee method called");
    }
}

public class ClassAdapter extends Adaptee implements Target {
    public void request() {
        specificRequest();
    }
}

public class ObjectAdapter implements Target {
    private Adaptee adaptee;
    public ObjectAdapter(Adaptee a) { this.adaptee = a; }
    public void request() {
        adaptee.specificRequest();
    }
}

3. Bridge Pattern

Definition: Decouples an abstraction from its implementation so that the two can vary independently.

Advantages:

Improves extensibility by separating abstraction and implementation.

Clients are insulated from implementation details.

Disadvantages:

Increases design complexity.

Structure:

Key Roles:

Abstraction – defines high‑level control logic.

RefinedAbstraction – extends Abstraction.

Implementor – defines low‑level interface.

ConcreteImplementor – concrete implementation of Implementor.

Sample Code (Java):

public interface Implementor {
    void operationImpl();
}

public class ConcreteImplementorA implements Implementor {
    public void operationImpl() {
        System.out.println("ConcreteImplementorA operation");
    }
}

public abstract class Abstraction {
    protected Implementor impl;
    protected Abstraction(Implementor impl) { this.impl = impl; }
    public abstract void operation();
}

public class RefinedAbstraction extends Abstraction {
    public RefinedAbstraction(Implementor impl) { super(impl); }
    public void operation() {
        System.out.println("RefinedAbstraction delegating");
        impl.operationImpl();
    }
}

4. Decorator Pattern

Definition: Dynamically adds responsibilities to objects without affecting other objects.

Advantages:

More flexible than subclassing.

Allows combination of multiple behaviors.

Disadvantages:

Can lead to many small classes, increasing complexity.

Structure:

Key Roles:

Component – defines the interface for objects.

ConcreteComponent – original object.

Decorator – abstract class that implements Component and holds a Component reference.

ConcreteDecorator – adds behavior.

Sample Code (Java):

public interface Component {
    void operation();
}

public class ConcreteComponent implements Component {
    public void operation() {
        System.out.println("ConcreteComponent operation");
    }
}

public abstract class Decorator implements Component {
    protected Component component;
    public Decorator(Component c) { this.component = c; }
    public void operation() { component.operation(); }
}

public class ConcreteDecorator extends Decorator {
    public ConcreteDecorator(Component c) { super(c); }
    public void operation() {
        super.operation();
        addedBehavior();
    }
    private void addedBehavior() {
        System.out.println("Added behavior");
    }
}

5. Facade Pattern

Definition: Provides a unified interface to a set of interfaces in a subsystem, making the subsystem easier to use.

Advantages:

Reduces coupling between client and subsystem.

Simplifies usage of complex subsystems.

Improves maintainability.

Disadvantages:

Can become a god object if overused.

Adding new subsystems may require changes to the facade.

Structure:

Key Roles:

Facade – the single entry point.

Subsystem classes – the complex components.

Sample Code (Java):

public class SubsystemA {
    public void operationA() { System.out.println("SubsystemA operation"); }
}
public class SubsystemB {
    public void operationB() { System.out.println("SubsystemB operation"); }
}
public class Facade {
    private SubsystemA a = new SubsystemA();
    private SubsystemB b = new SubsystemB();
    public void operation() {
        a.operationA();
        b.operationB();
    }
}
public class FacadeDemo {
    public static void main(String[] args) {
        Facade f = new Facade();
        f.operation();
    }
}

6. Flyweight Pattern

Definition: Uses sharing to support large numbers of fine‑grained objects efficiently.

Advantages:

Reduces memory consumption by sharing intrinsic state.

Disadvantages:

External (extrinsic) state must be managed separately, increasing complexity.

May incur a slight performance cost for retrieving shared objects.

Structure:

Key Roles:

Flyweight – declares methods that accept extrinsic state.

ConcreteFlyweight – implements Flyweight and stores intrinsic state.

UnsharedConcreteFlyweight – holds extrinsic state.

FlyweightFactory – creates and manages Flyweight objects.

Sample Code (Java):

public interface Flyweight {
    void operation(UnsharedConcreteFlyweight state);
}

public class ConcreteFlyweight implements Flyweight {
    private String key;
    public ConcreteFlyweight(String key) {
        this.key = key;
        System.out.println("ConcreteFlyweight " + key + " created");
    }
    public void operation(UnsharedConcreteFlyweight state) {
        System.out.println("ConcreteFlyweight " + key + " used, extrinsic info: " + state.getInfo());
    }
}

public class UnsharedConcreteFlyweight {
    private String info;
    public UnsharedConcreteFlyweight(String info) { this.info = info; }
    public String getInfo() { return info; }
}

public class FlyweightFactory {
    private Map<String, Flyweight> pool = new HashMap<>();
    public Flyweight getFlyweight(String key) {
        Flyweight fw = pool.get(key);
        if (fw == null) {
            fw = new ConcreteFlyweight(key);
            pool.put(key, fw);
        } else {
            System.out.println("ConcreteFlyweight " + key + " already exists, retrieved");
        }
        return fw;
    }
}

7. Composite Pattern

Definition: Composes objects into tree structures to represent part‑whole hierarchies, allowing clients to treat individual objects and compositions uniformly.

Advantages:

Simplifies client code by treating leaf and composite uniformly.

Facilitates adding new components without changing existing code.

Disadvantages:

Design can become complex.

Hard to restrict which components can be added to a composite.

Structure (Transparent version):

Key Roles:

Component – declares interface for leaf and composite.

Leaf – represents end objects; implements Component.

Composite – stores child Components and implements Component.

Sample Code (Java – Transparent version):

public interface Component {
    void add(Component c);
    void remove(Component c);
    Component getChild(int i);
    void operation();
}

public class Leaf implements Component {
    private String name;
    public Leaf(String name) { this.name = name; }
    public void add(Component c) {}
    public void remove(Component c) {}
    public Component getChild(int i) { return null; }
    public void operation() {
        System.out.println("Leaf " + name + " accessed");
    }
}

public class Composite implements Component {
    private List<Component> children = new ArrayList<>();
    public void add(Component c) { children.add(c); }
    public void remove(Component c) { children.remove(c); }
    public Component getChild(int i) { return children.get(i); }
    public void operation() {
        for (Component c : children) {
            c.operation();
        }
    }
}

Using the composite:

Component root = new Composite();
Component leaf1 = new Leaf("1");
root.add(leaf1);
Component sub = new Composite();
root.add(sub);
sub.add(new Leaf("2"));
sub.add(new Leaf("3"));
root.operation(); // prints leaf 1, leaf 2, leaf 3

Conclusion

The structural patterns covered—Proxy, Adapter, Bridge, Decorator, Facade, Flyweight, and Composite—provide powerful tools for building flexible, maintainable, and efficient software architectures. By understanding their intent, structure, and trade‑offs, developers can select the appropriate pattern to solve common design problems and improve code quality.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Intelligent Backend & Architecture
Written by

Intelligent Backend & Architecture

We share personal insights on intelligent, automated backend technologies, along with practical AI knowledge, algorithms, and architecture design, grounded in real business scenarios.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.