Design Patterns in Java
Introduction
Design patterns are common solutions to recurring design problems in software development. In Java, design patterns are widely used to solve common problems efficiently, improve code readability, and promote reusability. In this tutorial, we will explore some fundamental design patterns: Singleton, Factory, and others.
We will provide step-by-step examples to understand these design patterns and their implementations.
Step 1: Singleton Pattern
The Singleton Pattern ensures that a class has only one instance and provides a global point of access to that instance. It is commonly used in situations where a single instance is needed to coordinate actions across the system, such as database connections or logging.
// Singleton Pattern Example
public class Singleton {
private static Singleton instance;
// Private constructor to prevent instantiation
private Singleton() {}
// Public method to get the instance
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
public void display() {
System.out.println("Singleton Instance: " + this);
}
public static void main(String[] args) {
// Access the Singleton instance
Singleton singleton1 = Singleton.getInstance();
singleton1.display();
Singleton singleton2 = Singleton.getInstance();
singleton2.display();
System.out.println("Are both instances equal? " + (singleton1 == singleton2));
}
}
In this example:
- The constructor of the class
Singletonis private to prevent creating instances directly. - The
getInstance()method returns the only instance of the class. - We demonstrate that both references point to the same instance.
Step 2: Factory Pattern
The Factory Pattern provides an interface for creating objects, but allows subclasses to alter the type of objects that will be created. It is useful when the creation process of an object is complex or requires a specific subclass of a generic class.
// Factory Pattern Example
abstract class Animal {
public abstract void speak();
}
class Dog extends Animal {
public void speak() {
System.out.println("Woof");
}
}
class Cat extends Animal {
public void speak() {
System.out.println("Meow");
}
}
class AnimalFactory {
public Animal getAnimal(String animalType) {
if (animalType == null) {
return null;
}
if (animalType.equalsIgnoreCase("DOG")) {
return new Dog();
} else if (animalType.equalsIgnoreCase("CAT")) {
return new Cat();
}
return null;
}
}
public class Main {
public static void main(String[] args) {
AnimalFactory factory = new AnimalFactory();
// Create a Dog object
Animal dog = factory.getAnimal("DOG");
dog.speak();
// Create a Cat object
Animal cat = factory.getAnimal("CAT");
cat.speak();
}
}
In this example:
- We define an abstract class
Animalwith an abstract methodspeak(). - We create concrete classes
DogandCatthat implement thespeak()method. - The
AnimalFactoryclass provides the methodgetAnimal()to create instances ofDogorCat. - The client uses the factory to create instances of different animals without directly calling their constructors.
Step 3: Observer Pattern
The Observer Pattern allows a subject to notify its observers when there is a change in its state. It is commonly used in event-driven systems, such as GUI applications or publishing/subscribing models.
// Observer Pattern Example
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;
}
@Override
public void update(String message) {
System.out.println(name + " received message: " + message);
}
}
class Subject {
private List<Observer> observers = new ArrayList<>();
public void addObserver(Observer observer) {
observers.add(observer);
}
public void removeObserver(Observer observer) {
observers.remove(observer);
}
public void notifyObservers(String message) {
for (Observer observer : observers) {
observer.update(message);
}
}
}
public class Main {
public static void main(String[] args) {
Subject subject = new Subject();
// Create observers
Observer observer1 = new ConcreteObserver("Observer1");
Observer observer2 = new ConcreteObserver("Observer2");
// Add observers to the subject
subject.addObserver(observer1);
subject.addObserver(observer2);
// Notify observers
subject.notifyObservers("New update available!");
}
}
In this example:
- The
Observerinterface defines theupdate()method that will be called when the subject changes. - The
ConcreteObserverclass implements theObserverinterface and provides its own implementation of theupdate()method. - The
Subjectclass maintains a list of observers and notifies them when there is a change. - We demonstrate how multiple observers are notified when the subject's state changes.
Step 4: Strategy Pattern
The Strategy Pattern allows the behavior of a class to be selected at runtime. It is useful when different algorithms can be applied to a problem, but the client does not need to know the specific algorithm being used.
// Strategy Pattern Example
interface Strategy {
void execute();
}
class ConcreteStrategyA implements Strategy {
public void execute() {
System.out.println("Executing Strategy A");
}
}
class ConcreteStrategyB implements Strategy {
public void execute() {
System.out.println("Executing Strategy B");
}
}
class Context {
private Strategy strategy;
public Context(Strategy strategy) {
this.strategy = strategy;
}
public void setStrategy(Strategy strategy) {
this.strategy = strategy;
}
public void executeStrategy() {
strategy.execute();
}
}
public class Main {
public static void main(String[] args) {
Context context = new Context(new ConcreteStrategyA());
context.executeStrategy();
context.setStrategy(new ConcreteStrategyB());
context.executeStrategy();
}
}
In this example:
- The
Strategyinterface defines theexecute()method that different strategies will implement. - We create two concrete strategies:
ConcreteStrategyAandConcreteStrategyB. - The
Contextclass holds a reference to a strategy and provides a method to execute the strategy. - We demonstrate how the strategy can be changed at runtime using the
setStrategy()method.
Conclusion
Design patterns are proven solutions to common software design problems. In this tutorial, we have covered:
- The Singleton Pattern: Ensures a class has only one instance.
- The Factory Pattern: Provides a way to create objects without specifying the exact class.
- The Observer Pattern: Allows a subject to notify multiple observers about changes.
- The Strategy Pattern: Allows the behavior of a class to be selected at runtime.