Structural design patterns deal with object composition and relationships, making it easier to build flexible and scalable systems. They focus on how objects and classes can be combined to form larger structures while ensuring that these structures remain efficient and maintainable.
Key Structural Design Patterns with Examples
1. Adapter Pattern
Acts as a bridge between incompatible interfaces by converting one interface into another that a client expects.
🎯 Goal:
Allow classes with incompatible interfaces to work together by creating a wrapper (adapter) that translates calls from one to the other.
🔄 Real-World Analogy:
Imagine you have a two-pin plug, but the socket is three-pin. You use a plug adapter to make them work together—you don’t change the plug or the socket, just use an adapter in between.
Steps to Implement
- Identify two incompatible interfaces.
- Create an adapter class that implements the target interface and wraps the adaptee.
- Forward client requests to the adaptee via the adapter.
Java Example
// Existing class (incompatible interface)
class OldCharger {
public void chargeWithFlatPin() {
System.out.println("Charging with flat pin...");
}
}
//Target Interface (what the client expects)
interface Charger {
void charge();
}
//Adapter
class ChargerAdapter implements Charger {
private OldCharger oldCharger;
public ChargerAdapter(OldCharger oldCharger) {
this.oldCharger = oldCharger;
}
@Override
public void charge() {
// Convert expected method to actual method
oldCharger.chargeWithFlatPin();
}
}
//Client Code
public class AdapterDemo {
public static void main(String[] args) {
OldCharger oldCharger = new OldCharger();
Charger charger = new ChargerAdapter(oldCharger); // Use adapter
charger.charge(); // Charging with flat pin...
}
}
Spring Framework Example
- Spring MVC: Adapters like
HandlerAdapterallow controllers with different interfaces to work with the Spring MVC framework.
Real-World Use Case
- Legacy Systems: Adapting legacy APIs to work with modern interfaces.
Advantages
- Increases code reusability.
- Enables compatibility without changing existing code.
Disadvantages
- Can increase complexity if overused.
2. Bridge Pattern
Decouples abstraction from implementation, allowing the two to vary independently.
🧠 Key Idea:
Instead of hard-wiring an abstraction to its implementation using inheritance, the Bridge pattern uses composition to reference the implementation.
Steps to Implement
- Define an abstraction interface and its implementation interface.
- Implement the abstraction using a reference to the implementation interface.
- Implement multiple variants of both abstraction and implementation.
Java Example
interface Color {
void applyColor();
}
class RedColor implements Color {
public void applyColor() {
System.out.println("Applying red color");
}
}
abstract class Shape {
protected Color color;
public Shape(Color color) {
this.color = color;
}
abstract void draw();
}
class Circle extends Shape {
public Circle(Color color) {
super(color);
}
public void draw() {
System.out.print("Drawing Circle with ");
color.applyColor();
}
}
Spring Framework Example
- Spring JDBC:
JdbcTemplateabstracts database operations, separating query logic from connection handling.
Real-World Use Case
- Graphics Libraries: Separating shape hierarchies from rendering hierarchies.
Advantages
- Promotes flexibility and scalability.
- Avoids a combinatorial explosion of classes.
Disadvantages
- Adds complexity with additional layers.
Bridge vs Adapter vs Strategy:
| Pattern | Purpose | Key Technique |
|---|---|---|
| Bridge | Split abstraction from implementation | Composition |
| Adapter | Make incompatible interfaces work | Interface translation |
| Strategy | Interchangeable algorithms | Interface + composition |
3. Composite Pattern
“Combines objects into tree-like structures to represent part-whole hierarchies so that clients can treat individual objects and compositions uniformly.”
Steps to Implement
- Define a common interface for all components.
- Implement leaf nodes and composite nodes that can hold child components.
- Use the composite structure as if it were a single object.
🧠 Real-World Analogy:
Think of a folder on your computer:
- It can contain files (leaf nodes).
- It can also contain other folders (composites). You can treat both the same — open them, get size, rename, delete, etc.
Java Example
//Step 1: Common Component Interface
interface FileSystemComponent {
void showDetails();
}
//Step 2: Leaf Class (File)
class File implements FileSystemComponent {
private String name;
public File(String name) {
this.name = name;
}
public void showDetails() {
System.out.println("File: " + name);
}
}
//Step 3: Composite Class (Folder)
import java.util.ArrayList;
import java.util.List;
class Folder implements FileSystemComponent {
private String name;
private List<FileSystemComponent> children = new ArrayList<>();
public Folder(String name) {
this.name = name;
}
public void add(FileSystemComponent component) {
children.add(component);
}
public void showDetails() {
System.out.println("Folder: " + name);
for (FileSystemComponent component : children) {
component.showDetails();
}
}
}
Step 4: Usage
public class CompositeDemo {
public static void main(String[] args) {
File file1 = new File("Resume.pdf");
File file2 = new File("Photo.jpg");
Folder folder1 = new Folder("Documents");
folder1.add(file1);
folder1.add(file2);
File file3 = new File("Music.mp3");
Folder rootFolder = new Folder("Root");
rootFolder.add(folder1);
rootFolder.add(file3);
rootFolder.showDetails();
}
}
Spring Framework Example
- Spring Security: Authorization rules can be grouped into a composite structure.
Real-World Use Case
- UI Components: Trees of graphical elements like buttons and containers.
Advantages
- Simplifies client code by treating individual and composite objects uniformly.
Disadvantages
- Can make code more complex with many small classes.
4. Decorator Pattern
Adds responsibilities to an object dynamically without altering its structure.
Steps to Implement
- Define a common interface for the base component and decorators.
- Create concrete decorators that wrap the base component and add new behavior.
Java Example
interface Coffee {
String getDescription();
double getCost();
}
class SimpleCoffee implements Coffee {
public String getDescription() {
return "Simple Coffee";
}
public double getCost() {
return 5.0;
}
}
class MilkDecorator implements Coffee {
private Coffee coffee;
public MilkDecorator(Coffee coffee) {
this.coffee = coffee;
}
public String getDescription() {
return coffee.getDescription() + ", Milk";
}
public double getCost() {
return coffee.getCost() + 1.5;
}
}
Spring Framework Example
- Spring Security: Filters are decorated to add additional functionality dynamically.
Real-World Use Case
- Text Editors: Adding features like spell-check or formatting to text objects.
Advantages
- Promotes open/closed principle.
- Enables flexible, dynamic behavior changes.
Disadvantages
- Can create a large number of small, similar classes.
5. Facade Pattern
Provides a simplified interface to a complex subsystem.
Steps to Implement
- Identify the complex subsystems.
- Create a facade class that provides a simplified API by interacting with the subsystems.
Java Example
class SubsystemA {
public void operationA() {
System.out.println("SubsystemA operation");
}
}
class SubsystemB {
public void operationB() {
System.out.println("SubsystemB operation");
}
}
class Facade {
private SubsystemA subsystemA = new SubsystemA();
private SubsystemB subsystemB = new SubsystemB();
public void performOperations() {
subsystemA.operationA();
subsystemB.operationB();
}
}
Spring Framework Example
- JdbcTemplate: Provides a simplified interface for database operations.
Real-World Use Case
- Web Frameworks: Abstracting HTTP requests and responses.
Advantages
- Reduces coupling between clients and subsystems.
Disadvantages
- Can become a bottleneck if the facade class grows too large.
6. Flyweight Pattern
Reduces memory usage by sharing as much data as possible with similar objects.
Steps to Implement
- Identify the common state that can be shared.
- Create a flyweight factory to manage shared instances.
- Store extrinsic state externally and pass it to the flyweight when needed.
Java Example
class Flyweight {
private final String sharedState;
public Flyweight(String sharedState) {
this.sharedState = sharedState;
}
public void operation(String uniqueState) {
System.out.println("Shared: " + sharedState + ", Unique: " + uniqueState);
}
}
class FlyweightFactory {
private Map<String, Flyweight> flyweights = new HashMap<>();
public Flyweight getFlyweight(String key) {
flyweights.putIfAbsent(key, new Flyweight(key));
return flyweights.get(key);
}
}
Real-World Use Case
- Text Editors: Sharing character glyphs to reduce memory usage.
Advantages
- Greatly reduces memory consumption for similar objects.
Disadvantages
- Introduces complexity in managing extrinsic state.
7. Proxy Pattern
Provides a surrogate to control access to an object.
Steps to Implement
- Create an interface for the original object.
- Implement the original and proxy classes.
- The proxy controls access to the real object.
Java Example
interface Image {
void display();
}
class RealImage implements Image {
private String fileName;
public RealImage(String fileName) {
this.fileName = fileName;
loadImage();
}
private void loadImage() {
System.out.println("Loading image: " + fileName);
}
public void display() {
System.out.println("Displaying: " + fileName);
}
}
class ProxyImage implements Image {
private RealImage realImage;
private String fileName;
public ProxyImage(String fileName) {
this.fileName = fileName;
}
public void display() {
if (realImage == null) {
realImage = new RealImage(fileName);
}
realImage.display();
}
}
Spring Framework Example
- AOP (Aspect-Oriented Programming): Spring uses proxies to add cross-cutting concerns like logging and security.
Real-World Use Case
- Lazy Loading: Delaying object initialization until needed.
Advantages
- Adds control and flexibility to object access.
Disadvantages
- Can introduce additional overhead.
8. Private Class Data Pattern
“Restricts access to internal representation by encapsulating it within a dedicated data class, protecting it from unwanted modifications.”
🎯 Intent:
- Separate the data from the class that uses it.
- Only expose what is necessary.
- Prevent external (or even internal) modification of data after object creation.
✅ Key Concepts:
Often used with immutable fields.
A main class (e.g., User) handles logic/behavior.
A separate private data class (e.g., UserData) holds internal state.
The main class uses the data but doesn’t expose setters or allow direct changes.
Steps to Implement
- Define a class to hold private data.
- Provide methods to access or modify data securely.
Java Example
class UserData {
private final String username;
private final String email;
public UserData(String username, String email) {
this.username = username;
this.email = email;
}
public String getUsername() {
return username;
}
public String getEmail() {
return email;
}
}
class User {
private final UserData userData;
public User(String username, String email) {
this.userData = new UserData(username, email);
}
public void printInfo() {
System.out.println("Username: " + userData.getUsername());
System.out.println("Email: " + userData.getEmail());
}
}
public class PrivateClassDataDemo {
public static void main(String[] args) {
User user = new User("john_doe", "[email protected]");
user.printInfo();
}
}
Real-World Use Case
- Sensitive Information: Protecting access to user credentials.
Advantages
- Enforces encapsulation and information hiding.
Disadvantages
- Can require additional boilerplate code.
9. Front Controller Pattern
Centralizes request handling in web applications.
Steps to Implement
- Create a single controller to handle all incoming requests.
- Use the controller to dispatch requests to specific handlers.
Java Example
class FrontController {
public void dispatch(String request) {
if (request.equalsIgnoreCase("HOME")) {
new HomeView().show();
} else if (request.equalsIgnoreCase("PROFILE")) {
new ProfileView().show();
}
}
}
class HomeView {
public void show() {
System.out.println("Displaying Home Page");
}
}
class ProfileView {
public void show() {
System.out.println("Displaying Profile Page");
}
}
Spring Framework Example
- DispatcherServlet: Acts as a front controller in Spring MVC.
Real-World Use Case
- Web Frameworks: Centralizing HTTP request processing.
Advantages
- Simplifies request handling logic.
Disadvantages
- Can become a bottleneck if not designed well.
10. Module Pattern
Groups related code, exposing only required parts while hiding implementation details.
Steps to Implement
- Define a module with private and public methods.
- Expose only the necessary public methods.
Java Example
class Module {
private void privateMethod() {
System.out.println("Private Method");
}
public void publicMethod() {
System.out.println("Public Method");
privateMethod();
}
}
Real-World Use Case
- Package Management: Organizing code into modules with restricted visibility.
Advantages
- Promotes encapsulation and separation of concerns.
Disadvantages
- May require careful design to avoid excessive dependencies.
🏢 Facade Pattern
“Provides a unified interface to a set of interfaces in a subsystem. It defines a higher-level interface that makes the subsystem easier to use.”
🧠 Real-World Analogy:
Think of a hotel front desk:
- You don’t directly deal with housekeeping, kitchen, maintenance, etc.
- You go to the front desk (facade) and they coordinate everything behind the scenes.
✅ Java Example: Home Theater System
Step 1: Subsystems
class DVDPlayer {
void on() { System.out.println("DVD Player ON"); }
void play(String movie) { System.out.println("Playing: " + movie); }
void off() { System.out.println("DVD Player OFF"); }
}
class Projector {
void on() { System.out.println("Projector ON"); }
void setInput(DVDPlayer dvd) { System.out.println("Projector input set to DVD Player"); }
void off() { System.out.println("Projector OFF"); }
}
class SoundSystem {
void on() { System.out.println("Sound System ON"); }
void setVolume(int level) { System.out.println("Volume set to " + level); }
void off() { System.out.println("Sound System OFF"); }
}
Step 2: The Facade
class HomeTheaterFacade {
private DVDPlayer dvd;
private Projector projector;
private SoundSystem sound;
public HomeTheaterFacade(DVDPlayer dvd, Projector projector, SoundSystem sound) {
this.dvd = dvd;
this.projector = projector;
this.sound = sound;
}
public void watchMovie(String movie) {
System.out.println("Get ready to watch a movie...");
projector.on();
projector.setInput(dvd);
sound.on();
sound.setVolume(10);
dvd.on();
dvd.play(movie);
}
public void endMovie() {
System.out.println("Shutting down movie theater...");
dvd.off();
sound.off();
projector.off();
}
}
Step 3: Usage
public class FacadeDemo {
public static void main(String[] args) {
DVDPlayer dvd = new DVDPlayer();
Projector projector = new Projector();
SoundSystem sound = new SoundSystem();
HomeTheaterFacade homeTheater = new HomeTheaterFacade(dvd, projector, sound);
homeTheater.watchMovie("The Matrix");
homeTheater.endMovie();
}
}
✅ Output:
vbnetCopyEditGet ready to watch a movie...
Projector ON
Projector input set to DVD Player
Sound System ON
Volume set to 10
DVD Player ON
Playing: The Matrix
Shutting down movie theater...
DVD Player OFF
Sound System OFF
Projector OFF
🎯 When to Use:
- To simplify complex systems.
- To decouple client code from subsystem logic.
- To provide a single entry point to multiple classes (common in APIs, frameworks, libraries).
🛠 Real-World Examples in Code:
Java’s java.net.URL class: behind it, there’s DNS resolution, HTTP/HTTPS handling, sockets, etc., but it gives you a simple API.
Spring Boot’s @SpringBootApplication: wraps multiple annotations (like @Configuration, @ComponentScan, @EnableAutoConfiguration).