Java Functions

 

Java Design Patterns: Observer, Factory, and Singleton

Understanding Java Design Patterns

Design patterns are proven solutions to common design problems in software development. They provide a template for writing code that is modular, reusable, and easy to maintain. In this article, we’ll dive into three essential Java design patterns: Observer, Factory, and Singleton. We’ll explore their purposes, implementation, and practical examples to illustrate their use in real-world scenarios.

Java Design Patterns: Observer, Factory, and Singleton

 1. Observer Pattern

The Observer pattern is used to define a one-to-many dependency between objects, so that when one object changes state, all its dependents are notified and updated automatically. This pattern is ideal for implementing distributed event-handling systems.

 Example: Implementing the Observer Pattern

Here’s a simple example demonstrating the Observer pattern with a `Subject` and `Observer` interface.

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

// Observer interface
interface Observer {
    void update(String message);
}

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

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

    public void removeObserver(Observer observer) {
        observers.remove(observer);
    }

    public void setState(String state) {
        this.state = state;
        notifyObservers();
    }

    private void notifyObservers() {
        for (Observer observer : observers) {
            observer.update(state);
        }
    }
}

// Concrete Observer
class ConcreteObserver implements Observer {
    private String name;

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

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

// Main class
public class ObserverPatternExample {
    public static void main(String[] args) {
        Subject subject = new Subject();
        Observer observer1 = new ConcreteObserver("Observer1");
        Observer observer2 = new ConcreteObserver("Observer2");

        subject.addObserver(observer1);
        subject.addObserver(observer2);

        subject.setState("State1");
        subject.setState("State2");
    }
}
```

 2. Factory Pattern

The Factory pattern provides a way to delegate the instantiation of objects to subclasses, allowing for the creation of objects without specifying the exact class of the object that will be created. This pattern is useful for managing and maintaining a large number of related classes.

 Example: Implementing the Factory Pattern

Here’s an example of a simple factory method for creating different types of `Shape` objects.

```java
// Product interface
interface Shape {
    void draw();
}

// Concrete Products
class Circle implements Shape {
    @Override
    public void draw() {
        System.out.println("Drawing a Circle");
    }
}

class Rectangle implements Shape {
    @Override
    public void draw() {
        System.out.println("Drawing a Rectangle");
    }
}

// Factory class
class ShapeFactory {
    public static Shape getShape(String shapeType) {
        if (shapeType == null) {
            return null;
        }
        if (shapeType.equalsIgnoreCase("CIRCLE")) {
            return new Circle();
        } else if (shapeType.equalsIgnoreCase("RECTANGLE")) {
            return new Rectangle();
        }
        return null;
    }
}

// Main class
public class FactoryPatternExample {
    public static void main(String[] args) {
        Shape circle = ShapeFactory.getShape("CIRCLE");
        circle.draw();

        Shape rectangle = ShapeFactory.getShape("RECTANGLE");
        rectangle.draw();
    }
}
```

 3. Singleton Pattern

The Singleton pattern ensures that a class has only one instance and provides a global point of access to that instance. This pattern is useful for managing resources that should have a single, centralized point of access.

 Example: Implementing the Singleton Pattern

Here’s how you can implement a thread-safe Singleton class in Java.

```java
// Singleton class
class Singleton {
    private static Singleton instance;

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

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

    public void showMessage() {
        System.out.println("Hello from Singleton!");
    }
}

// Main class
public class SingletonPatternExample {
    public static void main(String[] args) {
        Singleton singleton = Singleton.getInstance();
        singleton.showMessage();
    }
}
```

Conclusion

Understanding and applying design patterns like Observer, Factory, and Singleton can significantly improve the design and flexibility of your Java applications. These patterns provide robust solutions to common design problems, making your code more modular, reusable, and easier to maintain. By leveraging these patterns, you can enhance the scalability and manageability of your software projects.

Further Reading:

  1. Design Patterns: Elements of Reusable Object-Oriented Software by Gamma et al.
  2. Java Design Patterns
  3. Refactoring Guru – Design Patterns
Previously at
Flag Argentina
Brazil
time icon
GMT-3
Experienced Senior Java Developer, Passionate about crafting robust solutions. 12 years of expertise in Java, Spring Boot, Angular, and microservices.