🔹 Structural Patterns in Software Design

**Structural design patterns** are concerned with how classes and objects are composed to form larger structures. These patterns help ensure that if one part of a system changes, the entire system does not need to do the same, promoting flexibility and reusability.

📌 Overview of Structural Patterns

Structural patterns focus on creating relationships between objects and classes to form larger structures. They help manage complexities by allowing the composition of objects in a more manageable way.

📌 Common Structural Patterns

  • ✅ **Adapter Pattern**
  • ✅ **Bridge Pattern**
  • ✅ **Composite Pattern**
  • ✅ **Decorator Pattern**
  • ✅ **Facade Pattern**
  • ✅ **Flyweight Pattern**
  • ✅ **Proxy Pattern**

🖥️ Adapter Pattern

The Adapter Pattern allows incompatible interfaces to work together. It acts as a bridge between two incompatible interfaces, enabling collaboration.


            class OldSystem {
                public void oldMethod() {
                    System.out.println("Old method implementation.");
                }
            }
            
            interface NewSystem {
                void newMethod();
            }
            
            class Adapter implements NewSystem {
                private OldSystem oldSystem;
            
                public Adapter(OldSystem oldSystem) {
                    this.oldSystem = oldSystem;
                }
            
                public void newMethod() {
                    oldSystem.oldMethod(); // Adapts the old method to the new method
                }
            }
                

🖥️ Bridge Pattern

The Bridge Pattern separates an abstraction from its implementation, allowing them to vary independently. This promotes flexibility and extensibility.


            interface Shape {
                void draw();
            }
            
            class Circle implements Shape {
                public void draw() {
                    System.out.println("Drawing a circle.");
                }
            }
            
            class Square implements Shape {
                public void draw() {
                    System.out.println("Drawing a square.");
                }
            }
            
            abstract class Color {
                protected Shape shape;
            
                public Color(Shape shape) {
                    this.shape = shape;
                }
            
                abstract void fill();
            }
            
            class Red extends Color {
                public Red(Shape shape) {
                    super(shape);
                }
            
                public void fill() {
                    System.out.print("Filling with red color. ");
                    shape.draw();
                }
            }
                

🖥️ Composite Pattern

The Composite Pattern allows you to compose objects into tree structures to represent part-whole hierarchies. It enables clients to treat individual objects and compositions uniformly.


            interface Component {
                void operation();
            }
            
            class Leaf implements Component {
                public void operation() {
                    System.out.println("Leaf operation.");
                }
            }
            
            class Composite implements Component {
                private List children = new ArrayList<>();
            
                public void add(Component component) {
                    children.add(component);
                }
            
                public void operation() {
                    for (Component child : children) {
                        child.operation();
                    }
                }
            }
                

🖥️ Decorator Pattern

The Decorator Pattern allows behavior to be added to individual objects dynamically. This pattern is often used to extend the functionality of classes in a flexible and reusable way.


            interface Coffee {
                String getDescription();
                double cost();
            }
            
            class SimpleCoffee implements Coffee {
                public String getDescription() {
                    return "Simple coffee";
                }
            
                public double cost() {
                    return 2.00;
                }
            }
            
            class MilkDecorator implements Coffee {
                private Coffee coffee;
            
                public MilkDecorator(Coffee coffee) {
                    this.coffee = coffee;
                }
            
                public String getDescription() {
                    return coffee.getDescription() + ", milk";
                }
            
                public double cost() {
                    return coffee.cost() + 0.50;
                }
            }
                

🖥️ Facade Pattern

The Facade Pattern provides a simplified interface to a complex subsystem, making it easier to use. This pattern promotes ease of use and reduces dependencies between clients and subsystems.


            class SubsystemA {
                public void operationA() {
                    System.out.println("Operation A");
                }
            }
            
            class SubsystemB {
                public void operationB() {
                    System.out.println("Operation B");
                }
            }
            
            class Facade {
                private SubsystemA subsystemA;
                private SubsystemB subsystemB;
            
                public Facade() {
                    subsystemA = new SubsystemA();
                    subsystemB = new SubsystemB();
                }
            
                public void operation() {
                    subsystemA.operationA();
                    subsystemB.operationB();
                }
            }
                

🖥️ Flyweight Pattern

The Flyweight Pattern is used to minimize memory usage by sharing common parts of state between multiple objects. This pattern is especially useful for large numbers of similar objects.


            class Character {
                private char symbol;
            
                public Character(char symbol) {
                    this.symbol = symbol;
                }
            
                public char getSymbol() {
                    return symbol;
                }
            }
            
            class CharacterFactory {
                private Map characters = new HashMap<>();
            
                public Character getCharacter(char symbol) {
                    if (!characters.containsKey(symbol)) {
                        characters.put(symbol, new Character(symbol));
                    }
                    return characters.get(symbol);
                }
            }
                

🖥️ Proxy Pattern

The Proxy Pattern provides a surrogate or placeholder for another object to control access to it. This pattern is often used for lazy initialization, access control, and logging.


            interface Image {
                void display();
            }
            
            class RealImage implements Image {
                private String filename;
            
                public RealImage(String filename) {
                    this.filename = filename;
                    loadImageFromDisk();
                }
            
                private void loadImageFromDisk() {
                    System.out.println("Loading " + 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();
                }
            }
                

🎯 Summary

Structural design patterns play a crucial role in software design by defining ways to compose objects and classes to form larger structures. By applying these patterns, developers can achieve greater flexibility and reusability in their code. Common structural patterns such as Adapter, Bridge, Composite, Decorator, Facade, Flyweight, and Proxy offer various solutions to manage complex relationships in software design.

🔗 Next Topics