Java Design Patterns: Singleton, Factory, and Observer

Java Design Patterns: Singleton, Factory, and Observer

Design patterns are proven solutions to common problems in software design. They provide a template for writing code that is easy to understand, maintain, and extend. In this article, we’ll explore three fundamental design patterns in Java: Singleton, Factory, and Observer. Each pattern addresses a specific type of problem and offers a structured approach to solving it.

Singleton Pattern

The Singleton pattern ensures that a class has only one instance and provides a global point of access to that instance. This is useful in scenarios where exactly one object is needed to coordinate actions across the system.

Example: Singleton Pattern

public class Singleton {
    private static Singleton instance;

    // Private constructor prevents instantiation from other classes
    private Singleton() {}

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

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

public class SingletonPatternDemo {
    public static void main(String[] args) {
        // Get the only object available
        Singleton singleton = Singleton.getInstance();

        // Show the message
        singleton.showMessage();
    }
}

Use Cases

  • Logging
  • Configuration settings
  • Connection pooling

Factory Pattern

The Factory pattern provides an interface for creating objects in a superclass but allows subclasses to alter the type of objects that will be created. This pattern is useful when the exact type of object to be created is determined at runtime.

Example: Factory Pattern

interface Shape {
    void draw();
}

class Circle implements Shape {
    public void draw() {
        System.out.println("Inside Circle::draw() method.");
    }
}

class Square implements Shape {
    public void draw() {
        System.out.println("Inside Square::draw() method.");
    }
}

class Rectangle implements Shape {
    public void draw() {
        System.out.println("Inside Rectangle::draw() method.");
    }
}

class ShapeFactory {
    // Use getShape method to get object of type Shape
    public Shape getShape(String shapeType) {
        if (shapeType == null) {
            return null;
        }
        if (shapeType.equalsIgnoreCase("CIRCLE")) {
            return new Circle();
        } else if (shapeType.equalsIgnoreCase("SQUARE")) {
            return new Square();
        } else if (shapeType.equalsIgnoreCase("RECTANGLE")) {
            return new Rectangle();
        }
        return null;
    }
}

public class FactoryPatternDemo {
    public static void main(String[] args) {
        ShapeFactory shapeFactory = new ShapeFactory();

        // Get an object of Circle and call its draw method
        Shape shape1 = shapeFactory.getShape("CIRCLE");
        shape1.draw();

        // Get an object of Square and call its draw method
        Shape shape2 = shapeFactory.getShape("SQUARE");
        shape2.draw();

        // Get an object of Rectangle and call its draw method
        Shape shape3 = shapeFactory.getShape("RECTANGLE");
        shape3.draw();
    }
}

Use Cases

  • When the exact type of the object cannot be determined until runtime.
  • Managing and maintaining a group of objects that have shared characteristics.

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. This pattern is mainly used to implement distributed event-handling systems.

Example: Observer Pattern

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

interface Observer {
    void update(String message);
}

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

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

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

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

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

class ConcreteObserver implements Observer {
    private String name;

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

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

public class ObserverPatternDemo {
    public static void main(String[] args) {
        Subject subject = new Subject();

        ConcreteObserver observer1 = new ConcreteObserver("Observer 1");
        ConcreteObserver observer2 = new ConcreteObserver("Observer 2");

        subject.attach(observer1);
        subject.attach(observer2);

        subject.setState("State changed!");

        subject.detach(observer1);

        subject.setState("State changed again!");
    }
}

Use Cases

  • Implementing event handling systems.
  • Notifying subscribers when a specific event occurs.
  • Model-view-controller (MVC) frameworks.

Conclusion

Design patterns provide a structured approach to solving common software design problems. The Singleton, Factory, and Observer patterns are fundamental patterns that every Java developer should understand. By incorporating these patterns into your code, you can create more flexible, maintainable, and robust applications.

Hashtags

#Java #DesignPatterns #SingletonPattern #FactoryPattern #ObserverPattern #SoftwareDevelopment #JavaProgramming #ProgrammingTips #Coding #JavaDesign #SoftwareArchitecture #SoftwareEngineering #ProgrammingPatterns

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *