Java Design Patterns: A Comprehensive Overview

Welcome, Java developers! In today’s post, we’re going to explore design patterns in Java. Design patterns are typical solutions to common problems in software design. They represent the best practices used by experienced object-oriented software developers, enabling you to write clean, reusable, and maintainable code.

What are Design Patterns?

Design patterns are not finished designs but templates for how to solve problems in various situations. They are categorized into three main types:

  • Creational Patterns: These patterns deal with object creation mechanisms, trying to create objects in a manner suitable to the situation. Examples include Singleton, Factory Method, and Abstract Factory.
  • Structural Patterns: These patterns deal with object composition, creating relationships between objects to form a larger structure. Examples include Adapter, Decorator, and Composite.
  • Behavioral Patterns: These patterns focus on communication between objects, what goes on between objects and how they operate together. Examples include Observer, Strategy, and Command.

Creational Patterns

1. Singleton Pattern

The Singleton pattern ensures that a class has only one instance and provides a global point of access to it.

public class Singleton {
    private static Singleton instance;

    private Singleton() {} // Private constructor to prevent instantiation

    public static Singleton getInstance() {
        if (instance == null) {
            instance = new Singleton();
        }
        return instance;
    }
}

In this example, getInstance() guarantees that only one instance of the class is created.

2. Factory Method Pattern

The Factory Method pattern defines an interface for creating an object, but let subclasses alter the type of objects that will be created.

interface Product {
    void use();
}

class ConcreteProductA implements Product {
    public void use() {
        System.out.println("Using Product A");
    }
}

class ConcreteProductB implements Product {
    public void use() {
        System.out.println("Using Product B");
    }
}

abstract class Creator {
    public abstract Product factoryMethod();
}

class ConcreteCreatorA extends Creator {
    public Product factoryMethod() {
        return new ConcreteProductA();
    }
}

class ConcreteCreatorB extends Creator {
    public Product factoryMethod() {
        return new ConcreteProductB();
    }
}

Here, each creator class instantiates its own product. When you need an object, you request it through the creator class.

Structural Patterns

1. Adapter Pattern

The Adapter pattern allows objects with incompatible interfaces to work together. It acts as a bridge between two incompatible interfaces.

interface Target {
    void request();
}

class Adaptee {
    void specificRequest() {
        System.out.println("Called specific request");
    }
}

class Adapter implements Target {
    private Adaptee adaptee;

    public Adapter(Adaptee adaptee) {
        this.adaptee = adaptee;
    }

    public void request() {
        adaptee.specificRequest();
    }
}

In this example, the Adapter converts the interface of the Adaptee to the Target interface.

2. Decorator Pattern

The Decorator pattern allows behavior to be added to individual objects, either statically or dynamically, without affecting the behavior of other objects from the same class.

interface Coffee {
    double cost();
}

class SimpleCoffee implements Coffee {
    public double cost() {
        return 5.0;
    }
}

abstract class CoffeeDecorator implements Coffee {
    protected Coffee decoratedCoffee;

    public CoffeeDecorator(Coffee coffee) {
        this.decoratedCoffee = coffee;
    }
}

class MilkDecorator extends CoffeeDecorator {
    public MilkDecorator(Coffee coffee) {
        super(coffee);
    }

    public double cost() {
        return decoratedCoffee.cost() + 1.0;
    }
}

The MilkDecorator enhances the SimpleCoffee by adding milk at an additional cost.

Behavioral Patterns

1. Observer Pattern

The Observer pattern defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.

import java.util.ArrayList;
import java.util.List;

interface Observer {
    void update(String message);
}

class ConcreteObserver implements Observer {
    private String name;

    public ConcreteObserver(String name) {
        this.name = name;
    }

    public void update(String message) {
        System.out.println(name + " received: " + message);
    }
}

class Subject {
    private List<Observer> observers = new ArrayList<>();

    public void addObserver(Observer observer) {
        observers.add(observer);
    }

    public void notifyObservers(String message) {
        for (Observer observer : observers) {
            observer.update(message);
        }
    }
}

In this example, when the subject’s state changes, all registered observers are notified with a message.

2. Strategy Pattern

The Strategy pattern enables selecting an algorithm’s behavior at runtime. It defines a family of algorithms, encapsulates each one, and makes them interchangeable.

interface Strategy {
    int doOperation(int num1, int num2);
}

class OperationAdd implements Strategy {
    public int doOperation(int num1, int num2) {
        return num1 + num2;
    }
}

class OperationSubtract implements Strategy {
    public int doOperation(int num1, int num2) {
        return num1 - num2;
    }
}

class Context {
    private Strategy strategy;

    public Context(Strategy strategy) {
        this.strategy = strategy;
    }

    public int executeStrategy(int num1, int num2) {
        return strategy.doOperation(num1, num2);
    }
}

Using the Context class, you can change the strategy at runtime to use different operations.

Conclusion

Java design patterns provide proven and efficient solutions to common software engineering problems. By understanding these patterns and when to apply them, you can significantly enhance the quality and maintainability of your code.

Want to learn more about Java Core? Join the Java Core in Practice course now!

To learn more about ITER Academy, visit our website.

Scroll to Top