Design patterns are general, reusable solutions to problems that occur frequently in software design. They represent the best practices used by experienced object - oriented software developers. Design patterns are not specific pieces of code but rather templates for how to solve a problem in various situations.
There are three main categories of design patterns:
The Singleton pattern ensures that a class has only one instance and provides a global point of access to it.
// Classic Singleton implementation
class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
// Usage
public class SingletonExample {
public static void main(String[] args) {
Singleton singleton1 = Singleton.getInstance();
Singleton singleton2 = Singleton.getInstance();
System.out.println(singleton1 == singleton2); // true
}
}
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.
// Interface for the product
interface Shape {
void draw();
}
// Concrete implementations
class Circle implements Shape {
@Override
public void draw() {
System.out.println("Drawing a circle");
}
}
class Square implements Shape {
@Override
public void draw() {
System.out.println("Drawing a square");
}
}
// Factory class
class ShapeFactory {
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();
}
return null;
}
}
// Usage
public class FactoryExample {
public static void main(String[] args) {
ShapeFactory shapeFactory = new ShapeFactory();
Shape circle = shapeFactory.getShape("CIRCLE");
circle.draw();
Shape square = shapeFactory.getShape("SQUARE");
square.draw();
}
}
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;
// Subject interface
interface Subject {
void registerObserver(Observer o);
void removeObserver(Observer o);
void notifyObservers();
}
// Concrete subject
class WeatherStation implements Subject {
private List<Observer> observers = new ArrayList<>();
private float temperature;
public void setTemperature(float temperature) {
this.temperature = temperature;
notifyObservers();
}
@Override
public void registerObserver(Observer o) {
observers.add(o);
}
@Override
public void removeObserver(Observer o) {
observers.remove(o);
}
@Override
public void notifyObservers() {
for (Observer observer : observers) {
observer.update(temperature);
}
}
}
// Observer interface
interface Observer {
void update(float temperature);
}
// Concrete observer
class TemperatureDisplay implements Observer {
@Override
public void update(float temperature) {
System.out.println("Temperature updated: " + temperature);
}
}
// Usage
public class ObserverExample {
public static void main(String[] args) {
WeatherStation weatherStation = new WeatherStation();
TemperatureDisplay display = new TemperatureDisplay();
weatherStation.registerObserver(display);
weatherStation.setTemperature(25.5f);
}
}
getInstance
method.Design patterns are powerful tools for Java developers. They provide solutions to common software design problems, making the code more modular, maintainable, and scalable. By understanding the fundamental concepts, usage methods, common practices, and best practices of design patterns, developers can write high - quality Java code. However, it’s important to use design patterns judiciously and not over - complicate the code.