Design Patterns Catalog: Singleton, Observer, Factory, Adapter & More
Related Design Patterns Articles:
- Design Patterns Fundamentals (GoF)
- Creational Patterns (Singleton, Factory, Builder, Prototype, Abstract Factory)
- Design Patterns Overview
This article is a comprehensive catalog of the most important Design Patterns – including Singleton, Observer, Factory, Adapter, Iterator, Strategy, Decorator, and Template Method with practical implementations.
In a Nutshell
Design Patterns are proven solution templates for recurring problems in software development. They enable you to build reusable, maintainable, and extensible architectures.
Technical Overview
Design Patterns (or Entwurfsmuster) are documented solutions for typical problems in object-oriented software development. They were categorized by the “Gang of Four” (GoF) and represent best practices proven in the field.
GoF Pattern Categories:
Creational Patterns
- Singleton: Ensures only one instance of a class exists
- Factory: Creates objects without direct instantiation
- Abstract Factory: Handles families of related objects
- Builder: Constructs complex objects step by step
Structural Patterns
- Adapter: Makes incompatible interfaces work together
- Decorator: Adds functionality dynamically
- Facade: Provides a simplified interface for subsystems
- Proxy: Acts as a stand-in for an object
Behavioral Patterns
- Observer: Notifies objects when state changes
- Strategy: Makes algorithms interchangeable
- Iterator: Provides sequential access to collections
- Template Method: Defines an algorithm’s skeleton
Key Exam Topics
- Singleton: Global instance, thread-safe implementation
- Observer: Subject-observer relationship, loose coupling
- Factory: Centralized object creation, polymorphism
- Adapter: Interface adaptation, composition vs. inheritance
- Iterator: Uniform access to containers, hasNext/next
- Strategy: Interchangeable algorithms, Open-Closed Principle
- Decorator: Function enrichment, dynamic behavior
- Template Method: Algorithm skeleton, hook methods
- IHK-relevant: Essential for software architecture and design
Core Components
- Singleton: One instance per class
- Observer: 1-to-many relationship for notifications
- Factory: Object creation without the new operator
- Adapter: Interface adaptation
- Iterator: Sequential access to collections
- Strategy: Swappable algorithms
- Decorator: Dynamic functionality enhancement
- Template Method: Algorithm skeleton with variable steps
Practical Examples
1. Singleton Pattern
// Thread-safe Singleton with Lazy Initialization
public class DatabaseConnection {
private static volatile DatabaseConnection instance;
private final String connectionUrl;
// Privater Konstruktor verhindert externe Instanziierung
private DatabaseConnection() {
this.connectionUrl = "jdbc:mysql://localhost:3306/mydb";
// Simulierte Verbindungsaufbau
System.out.println("Datenbankverbindung hergestellt");
}
// Double-Checked Locking für Thread-Safety
public static DatabaseConnection getInstance() {
if (instance == null) {
synchronized (DatabaseConnection.class) {
if (instance == null) {
instance = new DatabaseConnection();
}
}
}
return instance;
}
public void executeQuery(String sql) {
System.out.println("Führe aus: " + sql);
}
public String getConnectionUrl() {
return connectionUrl;
}
}
// Verwendung
public class SingletonDemo {
public static void main(String[] args) {
DatabaseConnection conn1 = DatabaseConnection.getInstance();
DatabaseConnection conn2 = DatabaseConnection.getInstance();
System.out.println("Gleiches Objekt: " + (conn1 == conn2)); // true
System.out.println("Connection URL: " + conn1.getConnectionUrl());
conn1.executeQuery("SELECT * FROM users");
}
}
2. Observer Pattern
import java.util.*;
// Subject (Observable)
interface Subject {
void registerObserver(Observer observer);
void removeObserver(Observer observer);
void notifyObservers();
}
// Concrete Subject
class WeatherStation implements Subject {
private List<Observer> observers = new ArrayList<>();
private float temperature;
private float humidity;
public void setMeasurements(float temperature, float humidity) {
this.temperature = temperature;
this.humidity = humidity;
measurementsChanged();
}
public void measurementsChanged() {
notifyObservers();
}
@Override
public void registerObserver(Observer observer) {
observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyObservers() {
for (Observer observer : observers) {
observer.update(temperature, humidity);
}
}
}
// Observer
interface Observer {
void update(float temperature, float humidity);
}
// Concrete Observer
class TemperatureDisplay implements Observer {
private float temperature;
@Override
public void update(float temperature, float humidity) {
this.temperature = temperature;
display();
}
public void display() {
System.out.println("Temperaturanzeige: " + temperature + "°C");
}
}
class HumidityDisplay implements Observer {
private float humidity;
@Override
public void update(float temperature, float humidity) {
this.humidity = humidity;
display();
}
public void display() {
System.out.println("Feuchtigkeitsanzeige: " + humidity + "%");
}
}
// Verwendung
public class ObserverDemo {
public static void main(String[] args) {
WeatherStation weatherStation = new WeatherStation();
TemperatureDisplay tempDisplay = new TemperatureDisplay();
HumidityDisplay humidityDisplay = new HumidityDisplay();
// Observer registrieren
weatherStation.registerObserver(tempDisplay);
weatherStation.registerObserver(humidityDisplay);
// Messungen ändern
weatherStation.setMeasurements(25.5f, 65.0f);
weatherStation.setMeasurements(26.0f, 63.5f);
// Observer entfernen
weatherStation.removeObserver(humidityDisplay);
weatherStation.setMeasurements(24.8f, 67.2f);
}
}
3. Factory Pattern
// Product Interface
interface Shape {
void draw();
}
// Concrete Products
class Circle implements Shape {
@Override
public void draw() {
System.out.println("Zeichne Kreis");
}
}
class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("Zeichne Rechteck");
}
}
class Triangle implements Shape {
@Override
public void draw() {
System.out.println("Zeichne Dreieck");
}
}
// Factory
class ShapeFactory {
public Shape createShape(String shapeType) {
if (shapeType == null || shapeType.isEmpty()) {
return null;
}
switch (shapeType.toUpperCase()) {
case "CIRCLE":
return new Circle();
case "RECTANGLE":
return new Rectangle();
case "TRIANGLE":
return new Triangle();
default:
throw new IllegalArgumentException("Unbekannter Shape-Typ: " + shapeType);
}
}
}
// Verwendung
public class FactoryDemo {
public static void main(String[] args) {
ShapeFactory factory = new ShapeFactory();
// Shapes erstellen ohne new-Operator
Shape circle = factory.createShape("CIRCLE");
Shape rectangle = factory.createShape("RECTANGLE");
Shape triangle = factory.createShape("TRIANGLE");
// Shapes verwenden
circle.draw();
rectangle.draw();
triangle.draw();
// Factory mit Konfiguration
createShapesFromConfig(factory);
}
private static void createShapesFromConfig(ShapeFactory factory) {
String[] shapes = {"CIRCLE", "RECTANGLE", "CIRCLE", "TRIANGLE"};
for (String shapeType : shapes) {
try {
Shape shape = factory.createShape(shapeType);
shape.draw();
} catch (IllegalArgumentException e) {
System.err.println("Fehler: " + e.getMessage());
}
}
}
}
4. Adapter Pattern
// Target Interface
interface MediaPlayer {
void play(String audioType, String fileName);
}
// Adaptee (incompatible interface)
interface AdvancedMediaPlayer {
void playVlc(String fileName);
void playMp4(String fileName);
}
// Concrete Adaptee
class VlcPlayer implements AdvancedMediaPlayer {
@Override
public void playVlc(String fileName) {
System.out.println("Playing VLC file: " + fileName);
}
@Override
public void playMp4(String fileName) {
// Not supported
}
}
class Mp4Player implements AdvancedMediaPlayer {
@Override
public void playVlc(String fileName) {
// Not supported
}
@Override
public void playMp4(String fileName) {
System.out.println("Playing MP4 file: " + fileName);
}
}
// Adapter
class MediaAdapter implements MediaPlayer {
private AdvancedMediaPlayer advancedMusicPlayer;
public MediaAdapter(String audioType) {
if (audioType.equalsIgnoreCase("vlc")) {
advancedMusicPlayer = new VlcPlayer();
} else if (audioType.equalsIgnoreCase("mp4")) {
advancedMusicPlayer = new Mp4Player();
}
}
@Override
public void play(String audioType, String fileName) {
if (audioType.equalsIgnoreCase("vlc")) {
advancedMusicPlayer.playVlc(fileName);
} else if (audioType.equalsIgnoreCase("mp4")) {
advancedMusicPlayer.playMp4(fileName);
}
}
}
// Client
class AudioPlayer implements MediaPlayer {
private MediaAdapter mediaAdapter;
@Override
public void play(String audioType, String fileName) {
// Support native formats
if (audioType.equalsIgnoreCase("mp3")) {
System.out.println("Playing MP3 file: " + fileName);
}
// Route other formats through adapter
else if (audioType.equalsIgnoreCase("vlc") || audioType.equalsIgnoreCase("mp4")) {
mediaAdapter = new MediaAdapter(audioType);
mediaAdapter.play(audioType, fileName);
} else {
System.out.println("Invalid media format: " + audioType);
}
}
}
// Usage
public class AdapterDemo {
public static void main(String[] args) {
AudioPlayer audioPlayer = new AudioPlayer();
audioPlayer.play("mp3", "song.mp3");
audioPlayer.play("mp4", "video.mp4");
audioPlayer.play("vlc", "movie.vlc");
audioPlayer.play("avi", "video.avi");
}
}
5. Iterator Pattern
import java.util.*;
// Aggregate Interface
interface Container {
Iterator getIterator();
}
// Iterator Interface
interface Iterator {
boolean hasNext();
Object next();
}
// Concrete Aggregate
class NameRepository implements Container {
private String[] names = {"Alice", "Bob", "Charlie", "Diana"};
@Override
public Iterator getIterator() {
return new NameIterator();
}
private class NameIterator implements Iterator {
private int index;
@Override
public boolean hasNext() {
return index < names.length;
}
@Override
public Object next() {
if (this.hasNext()) {
return names[index++];
}
return null;
}
}
}
// Usage
public class IteratorDemo {
public static void main(String[] args) {
NameRepository namesRepository = new NameRepository();
for (Iterator iterator = namesRepository.getIterator(); iterator.hasNext();) {
String name = (String) iterator.next();
System.out.println("Name: " + name);
}
// Comparison with Java standard Iterator
List<String> nameList = Arrays.asList("Alice", "Bob", "Charlie", "Diana");
System.out.println("\nUsing Java Iterator:");
for (Iterator<String> it = nameList.iterator(); it.hasNext();) {
String name = it.next();
System.out.println("Name: " + name);
}
// Enhanced for loop (syntactic sugar)
System.out.println("\nUsing enhanced for loop:");
for (String name : nameList) {
System.out.println("Name: " + name);
}
}
}
6. Strategy Pattern
// Strategy Interface
interface PaymentStrategy {
void pay(int amount);
}
// Concrete Strategies
class CreditCardPayment implements PaymentStrategy {
private String name;
private String cardNumber;
private String cvv;
private String date;
public CreditCardPayment(String name, String cardNumber, String cvv, String date) {
this.name = name;
this.cardNumber = cardNumber;
this.cvv = cvv;
this.date = date;
}
@Override
public void pay(int amount) {
System.out.println(amount + " € paid with credit card.");
System.out.println("Card holder: " + name);
System.out.println("Card number: **** **** **** " + cardNumber.substring(cardNumber.length() - 4));
}
}
class PayPalPayment implements PaymentStrategy {
private String email;
private String password;
public PayPalPayment(String email, String password) {
this.email = email;
this.password = password;
}
@Override
public void pay(int amount) {
System.out.println(amount + " € paid with PayPal.");
System.out.println("PayPal account: " + email);
}
}
class BitcoinPayment implements PaymentStrategy {
private String walletAddress;
public BitcoinPayment(String walletAddress) {
this.walletAddress = walletAddress;
}
@Override
public void pay(int amount) {
System.out.println(amount + " € paid with Bitcoin.");
System.out.println("Wallet: " + walletAddress.substring(0, 8) + "...");
}
}
// Context
class ShoppingCart {
private PaymentStrategy paymentStrategy;
public void setPaymentStrategy(PaymentStrategy paymentStrategy) {
this.paymentStrategy = paymentStrategy;
}
public void checkout(int amount) {
if (paymentStrategy == null) {
throw new IllegalStateException("No payment method selected");
}
paymentStrategy.pay(amount);
}
}
// Usage
public class StrategyDemo {
public static void main(String[] args) {
ShoppingCart cart = new ShoppingCart();
// Different payment methods
cart.setPaymentStrategy(new CreditCardPayment(
"Max Mustermann", "1234567890123456", "123", "12/25"));
cart.checkout(100);
System.out.println("---");
cart.setPaymentStrategy(new PayPalPayment("max@example.com", "password123"));
cart.checkout(250);
System.out.println("---");
cart.setPaymentStrategy(new BitcoinPayment("1A2B3C4D5E6F7G8H9I0J"));
cart.checkout(500);
// Dynamic strategy selection
dynamicStrategySelection(cart);
}
private static void dynamicStrategySelection(ShoppingCart cart) {
Scanner scanner = new Scanner(System.in);
System.out.println("\nSelect payment method:");
System.out.println("1. Credit card");
System.out.println("2. PayPal");
System.out.println("3. Bitcoin");
int choice = scanner.nextInt();
int amount = 75;
switch (choice) {
case 1:
cart.setPaymentStrategy(new CreditCardPayment(
"Test User", "1111222233334444", "999", "12/24"));
break;
case 2:
cart.setPaymentStrategy(new PayPalPayment("test@example.com", "test123"));
break;
case 3:
cart.setPaymentStrategy(new BitcoinPayment("ABCDEF123456789"));
break;
default:
System.out.println("Invalid choice");
return;
}
cart.checkout(amount);
scanner.close();
}
}
7. Decorator Pattern
// Component Interface
interface Coffee {
String getDescription();
double getCost();
}
// Concrete Component
class SimpleCoffee implements Coffee {
@Override
public String getDescription() {
return "Simple coffee";
}
@Override
public double getCost() {
return 2.0;
}
}
// Abstract Decorator
abstract class CoffeeDecorator implements Coffee {
protected Coffee decoratedCoffee;
public CoffeeDecorator(Coffee coffee) {
this.decoratedCoffee = coffee;
}
@Override
public String getDescription() {
return decoratedCoffee.getDescription();
}
@Override
public double getCost() {
return decoratedCoffee.getCost();
}
}
// Concrete Decorators
class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return super.getDescription() + ", milk";
}
@Override
public double getCost() {
return super.getCost() + 0.5;
}
}
class SugarDecorator extends CoffeeDecorator {
public SugarDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return super.getDescription() + ", sugar";
}
@Override
public double getCost() {
return super.getCost() + 0.2;
}
}
class WhippedCreamDecorator extends CoffeeDecorator {
public WhippedCreamDecorator(Coffee coffee) {
super(coffee);
}
@Override
public String getDescription() {
return super.getDescription() + ", whipped cream";
}
@Override
public double getCost() {
return super.getCost() + 1.0;
}
}
// Usage
public class DecoratorDemo {
public static void main(String[] args) {
// Simple coffee
Coffee coffee = new SimpleCoffee();
System.out.println(coffee.getDescription() + " costs " + coffee.getCost() + "€");
// With milk
coffee = new MilkDecorator(coffee);
System.out.println(coffee.getDescription() + " costs " + coffee.getCost() + "€");
// With sugar
coffee = new SugarDecorator(coffee);
System.out.println(coffee.getDescription() + " costs " + coffee.getCost() + "€");
// With whipped cream
coffee = new WhippedCreamDecorator(coffee);
System.out.println(coffee.getDescription() + " costs " + coffee.getCost() + "€");
// Flexible combination
Coffee customCoffee = new SimpleCoffee();
customCoffee = new MilkDecorator(customCoffee);
customCoffee = new SugarDecorator(customCoffee);
customCoffee = new SugarDecorator(customCoffee); // Double sugar
System.out.println("\nCustom coffee:");
System.out.println(customCoffee.getDescription() + " costs " + customCoffee.getCost() + "€");
}
}
8. Template Method Pattern
// Abstract Class with Template Method
abstract class DataProcessor {
// Template Method (final, so subclasses cannot override)
public final void processData() {
loadData();
if (validateData()) {
transformData();
saveData();
} else {
handleError();
}
cleanup();
}
// Abstract methods (must be implemented by subclasses)
protected abstract void loadData();
protected abstract void transformData();
protected abstract void saveData();
// Hook Methods (can be overridden)
protected boolean validateData() {
return true; // Default implementation
}
protected void handleError() {
System.out.println("Data validation failed!");
}
protected void cleanup() {
System.out.println("Cleanup completed");
}
}
// Concrete Implementation
class CSVDataProcessor extends DataProcessor {
private String data;
@Override
protected void loadData() {
System.out.println("Loading CSV data...");
data = "name,age,city\nAlice,25,Berlin\nBob,30,Hamburg";
}
@Override
protected void transformData() {
System.out.println("Transforming CSV data...");
// Convert CSV to JSON
data = data.replace("\n", ",").replace("name", "\"name\"")
.replace("age", "\"age\"").replace("city", "\"city\"");
}
@Override
protected void saveData() {
System.out.println("Saving transformed data...");
System.out.println("Data: " + data);
}
@Override
protected boolean validateData() {
// CSV-specific validation
return data != null && data.contains("name") && data.contains("age");
}
}
class XMLDataProcessor extends DataProcessor {
private String data;
@Override
protected void loadData() {
System.out.println("Loading XML data...");
data = "<users><user name='Alice' age='25'/><user name='Bob' age='30'/></users>";
}
@Override
protected void transformData() {
System.out.println("Transforming XML data...");
// Convert XML to JSON
data = "[{\"name\":\"Alice\",\"age\":25},{\"name\":\"Bob\",\"age\":30}]";
}
@Override
protected void saveData() {
System.out.println("Saving transformed data...");
System.out.println("Data: " + data);
}
@Override
protected void handleError() {
System.out.println("XML processing error!");
// Special error handling for XML
}
}
// Usage
public class TemplateMethodDemo {
public static void main(String[] args) {
System.out.println("=== CSV Processor ===");
DataProcessor csvProcessor = new CSVDataProcessor();
csvProcessor.processData();
System.out.println("\n=== XML Processor ===");
DataProcessor xmlProcessor = new XMLDataProcessor();
xmlProcessor.processData();
// Demonstration of the Template Method Pattern
demonstrateTemplateMethod();
}
private static void demonstrateTemplateMethod() {
System.out.println("\n=== Template Method Demonstration ===");
// The algorithm flow is always the same; only the implementation varies
DataProcessor[] processors = {
new CSVDataProcessor(),
new XMLDataProcessor()
};
for (DataProcessor processor : processors) {
System.out.println("\nProcessing with " + processor.getClass().getSimpleName() + ":");
processor.processData();
}
}
}
Pattern Categories Overview
Creational Patterns
| Pattern | Purpose | Example |
|---|---|---|
| Singleton | Ensure only one instance | Database Connection |
| Factory | Centralize object creation | Shape Factory |
| Abstract Factory | Create families of objects | GUI Factory |
| Builder | Construct complex objects step by step | StringBuilder |
Structural Patterns
| Pattern | Purpose | Example |
|---|---|---|
| Adapter | Reconcile incompatible interfaces | Media Adapter |
| Decorator | Add functionality dynamically | Coffee Decorator |
| Facade | Simplify complex interfaces | Home Theater |
| Proxy | Provide a stand-in | Virtual Proxy |
Behavioral Patterns
| Pattern | Purpose | Example |
|---|---|---|
| Observer | Notify on state changes | Weather Station |
| Strategy | Swap algorithms at runtime | Payment Strategy |
| Iterator | Access elements sequentially | Collection Iterator |
| Template Method | Define algorithm skeleton | Data Processor |
Benefits of Design Patterns
Reusability
- Leverage proven solutions across projects
- Cut development time
- Maintain architectural consistency
Maintainability
- Clear separation of concerns
- Loose coupling between components
- Easier to modify and extend
Communication
- Shared vocabulary among developers
- Well-documented approaches
- Faster code comprehension
Quality
- Battle-tested solutions
- Fewer bugs
- Higher code quality
Common Exam Questions
-
When should you use the Singleton Pattern? When you need exactly one instance of a class accessible globally (database connections, loggers).
-
What’s the difference between Adapter and Decorator? Adapter makes incompatible interfaces work together; Decorator adds behavior to existing objects.
-
Explain the Observer Pattern. A subject notifies observers when its state changes, keeping components loosely coupled.
-
Why is Template Method Pattern useful? It defines the skeleton of an algorithm, letting subclasses override specific steps without changing the algorithm’s structure.
Key Resources
- https://refactoring.guru/design-patterns
- https://www.oreilly.com/library/view/head-first-design/0596007124
- https://en.wikipedia.org/wiki/Software_design_pattern
Recommended Reading: Design Patterns
Design Patterns
Books about design patterns and software design
Design Patterns von Gang of Four
Bei Amazon ansehenAffiliate-Link: Bei einem Kauf erhalten wir möglicherweise eine Provision.
Patterns of Enterprise Application Architecture von Martin Fowler
Bei Amazon ansehenAffiliate-Link: Bei einem Kauf erhalten wir möglicherweise eine Provision.
Refactoring von Martin Fowler
Bei Amazon ansehenAffiliate-Link: Bei einem Kauf erhalten wir möglicherweise eine Provision.





