Structural design patterns are a set of design patterns that deal with organizing and composing objects to form larger structures. They are used to achieve flexibility, maintainability, and extensibility in software applications.
1. Adapter Pattern: The Adapter pattern allows objects with incompatible interfaces to work together. It acts as a bridge between them by translating the requests of one interface into the terms of another.
Example:
// Target interface
interface Target {
void request();
}
// Adaptee class
class Adaptee {
public void specificRequest() {
// Specific request implementation
}
}
// Adapter class
class Adapter implements Target {
private Adaptee adaptee;
public Adapter(Adaptee adaptee) {
this.adaptee = adaptee;
}
@Override
public void request() {
adaptee.specificRequest();
}
}
// Client class
public class Client {
public static void main(String[] args) {
Target target = new Adapter(new Adaptee());
target.request();
}
}
2. Bridge Pattern: The Bridge pattern decouples an abstraction from its implementation so that the two can vary independently. This allows you to change the implementation of an object without affecting the clients that use it.
Example:
// Abstraction interface
interface Abstraction {
void operation();
}
// Refined Abstraction class
class RefinedAbstraction extends Abstraction {
private Implementor implementor;
public RefinedAbstraction(Implementor implementor) {
this.implementor = implementor;
}
@Override
public void operation() {
implementor.operation();
}
}
// Implementor interface
interface Implementor {
void operation();
}
// Concrete Implementor class
class ConcreteImplementorA implements Implementor {
@Override
public void operation() {
// Concrete implementation A
}
}
// Concrete Implementor class
class ConcreteImplementorB implements Implementor {
@Override
public void operation() {
// Concrete implementation B
}
}
// Client class
public class Client {
public static void main(String[] args) {
Abstraction abstraction = new RefinedAbstraction(new ConcreteImplementorA());
abstraction.operation();
abstraction = new RefinedAbstraction(new ConcreteImplementorB());
abstraction.operation();
}
}
3. Composite Pattern: The Composite pattern composes objects into tree structures to represent part-whole hierarchies. It allows you to treat individual objects and composite objects (containing other objects) in a uniform manner.
Example:
// Component interface
interface Component {
void operation();
}
// Leaf class
class Leaf implements Component {
@Override
public void operation() {
// Leaf operation implementation
}
}
// Composite class
class Composite implements Component {
private List<Component> children = new ArrayList<>();
@Override
public void operation() {
for (Component child : children) {
child.operation();
}
}
public void add(Component component) {
children.add(component);
}
public void remove(Component component) {
children.remove(component);
}
}
// Client class
public class Client {
public static void main(String[] args) {
Component leaf1 = new Leaf();
Component leaf2 = new Leaf();
Component composite = new Composite();
composite.add(leaf1);
composite.add(leaf2);
composite.operation(); // Will call the operations of both leaf1 and leaf2
}
}
4. Decorator Pattern: The Decorator pattern dynamically adds or removes functionality to an object without changing its structure. It allows you to add new features to objects without creating new subclasses.
Example:
// Component interface
interface Component {
void operation();
}
// Concrete Component class
class ConcreteComponent implements Component {
@Override
public void operation() {
// Default operation implementation
}
}
// Decorator abstract class
abstract class Decorator implements Component {
private Component component;
public Decorator(Component component) {
this.component = component;
}
@Override
public void operation() {
component.operation();
}
public void addedBehavior() {
// Additional behavior added by the decorator
}
}
// Concrete Decorator class
class ConcreteDecoratorA extends Decorator {
public ConcreteDecoratorA(Component component) {
super(component);
}
@Override
public void operation() {
super.operation();
addedBehavior();
}
public void addedBehavior() {
// Additional behavior specific to ConcreteDecoratorA
}
}
// Client class
public class Client {
public static void main(String[] args) {
Component component = new ConcreteComponent();
Component decoratedComponent = new ConcreteDecoratorA(component);
decoratedComponent.operation(); // Will call the operation() methods of both ConcreteComponent and ConcreteDecoratorA
}
}
5. Facade Pattern: The Facade pattern provides a unified interface to a group of related classes, simplifying the interaction with that group. It hides the complexity of the underlying system and makes it easier for clients to interact with it.
Example:
// Facade class
class Facade {
private Subsystem1 subsystem1;
private Subsystem2 subsystem2;
public Facade() {
subsystem1 = new Subsystem1();
subsystem2 = new Subsystem2();
}
public void operation() {
subsystem1.operation1();
subsystem2.operation2();
}
}
// Subsystem1 class
class Subsystem1 {
public void operation1() {
// Subsystem1 operation implementation
}
}
// Subsystem2 class
class Subsystem2 {
public void operation2() {
// Subsystem2 operation implementation
}
}
// Client class
public class Client {
public static void main(String[] args) {
Facade facade = new Facade();
facade.operation(); // Calls operation1() of Subsystem1 and operation2() of Subsystem2
}
}
These are just a few examples of structural design patterns in Java. By applying these patterns effectively, you can improve the flexibility, maintainability, and extensibility of your software applications.