Singleton Pattern
Ensures a class has only one instance and provides global access to it. This avoids repeated instantiation of resource-heavy objects like database connections or logging services.
public class Logger {
private static volatile Logger instance;
private Logger() {}
public static Logger getInstance() {
if (instance == null) {
synchronized (Logger.class) {
if (instance == null) {
instance = new Logger();
}
}
}
return instance;
}
}
Common uses include centralized configuration managers, thread pools, and hardware interface handlers.
Factory Pattern
Delegates object creation to a dedicated factory class, hiding instantiation logic from the client. The client requests an object by type without knowing its concrete implementation.
public class VehicleFactory {
public Vehicle createVehicle(String type) {
switch (type.toLowerCase()) {
case "car": return new Car();
case "bike": return new Bike();
default: throw new IllegalArgumentException("Unknown vehicle type");
}
}
}
This pattern is ideal when the system needs to manage multiple implementations of a common interface—such as supporting various database drivers or payment gateways.
Strategy Pattern
Encapsulates interchangeable algorithms behind a common interface, allowing behavior to be selected at runtime without conditional branching.
interface CompressionStrategy {
byte[] compress(byte[] data);
}
class ZipStrategy implements CompressionStrategy {
public byte[] compress(byte[] data) {
// ZIP compression logic
return data; // placeholder
}
}
class GzipStrategy implements CompressionStrategy {
public byte[] compress(byte[] data) {
// GZIP compression logic
return data; // placeholder
}
}
class Compressor {
private CompressionStrategy strategy;
public Compressor(CompressionStrategy strategy) {
this.strategy = strategy;
}
public byte[] execute(byte[] input) {
return strategy.compress(input);
}
}
Useful when an application must dynamically switch between algorithms—like sorting methods, routing strategies, or pricing rules.
Observer Pattern
Defines a one-to-many dependency between objects sothat when one object changes state, all dependents are notified automatically.
import java.util.*;
interface EventListener {
void update(int value);
}
class DataSource {
private List<EventListener> listeners = new ArrayList<>();
private int data;
public void addListener(EventListener listener) {
listeners.add(listener);
}
public void setData(int value) {
this.data = value;
notifyListeners();
}
private void notifyListeners() {
for (EventListener l : listeners) {
l.update(data);
}
}
}
class HexDisplay implements EventListener {
public void update(int value) {
System.out.println("Hex: 0x" + Integer.toHexString(value).toUpperCase());
}
}
Widely used in event-driven systems such as GUI frameworks, publish-subscribe messaging, and real-time data dashboards.
Adapter Pattern
Converts the interface of a class into another interface clients expect, enabling classes with incompatible interfaces to collaborate.
interface MediaPlayer {
void play(String format, String file);
}
interface AdvancedMediaPlayer {
void playVlc(String file);
void playMp4(String file);
}
class VlcPlayer implements AdvancedMediaPlayer {
public void playVlc(String file) {
System.out.println("Playing VLC file: " + file);
}
public void playMp4(String file) {}
}
class MediaAdapter implements MediaPlayer {
private AdvancedMediaPlayer player;
public MediaAdapter(String format) {
if (format.equalsIgnoreCase("vlc")) {
player = new VlcPlayer();
} else if (format.equalsIgnoreCase("mp4")) {
player = new Mp4Player();
}
}
public void play(String format, String file) {
if (format.equalsIgnoreCase("vlc")) {
player.playVlc(file);
} else if (format.equalsIgnoreCase("mp4")) {
player.playMp4(file);
}
}
}
class AudioPlayer implements MediaPlayer {
public void play(String format, String file) {
if (format.equalsIgnoreCase("mp3")) {
System.out.println("Playing MP3: " + file);
} else if (format.equals("vlc") || format.equals("mp4")) {
new MediaAdapter(format).play(format, file);
} else {
System.out.println("Unsupported format: " + format);
}
}
}
Applied when integrating legacy components, third-party libraries, or systems with mismatched APIs.