Five Common Design Patterns in Object-Oriented Programming

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.

Tags: Design Patterns singleton factory strategy observer

Posted on Sat, 09 May 2026 04:41:49 +0000 by storyboo