Language: Java 21 (LTS) Tools: Maven 3.x, IntelliJ IDEA Ultimate (VS Code, Eclipse, Spring STS compatible)
Table of Contents
- Introduction
- Version check
- Concepts
- Design Features
- Everyday example — Runtime Environment
- Demo — Runtime Env
- Exercise — Create a Singleton
- Demo — Static Singleton (DbSingleton)
- Demo — Lazy Loaded Singleton (Initialization on Demand Holder)
- Demo — Database integration
- Pitfalls
- Pattern Comparison — Singleton vs Factory
- Summary
- Introduction
- Concepts
- Design Features
- Everyday example — StringBuilder
- Demo — StringBuilder
- Exercise — Create a Builder
- Demo — Immutability problem with setters (JavaBean)
- Demo — Telescoping Builders
- Demo — Builder Pattern (ComputerBuilder)
- Pitfalls
- Pattern Comparison — Builder vs Prototype
- Summary
- Introduction
- Concepts
- Design Features
- Daily example — Statement (shallow copy)
- Demo — Statement (shallow copy vs deep copy)
- Exercise — Create a Prototype
- Demo — Prototype with Registry (inventory system)
- Pitfalls
- Pattern comparison — Prototype vs Factory
- Summary
- Introduction
- Concepts
- Design Features
- Daily Example — Calendar
- Demo — Calendar
- Exercise — Create a Factory
- Demo — Website Factory (website generator)
- Demo — Replacing string literals with an Enum
- Pitfalls
- Pattern comparison — Factory vs Singleton
- Summary
- Introduction
- Concepts
- Design Features
- Everyday example — DocumentBuilderFactory
- Demo — DocumentBuilderFactory
- Exercise — Create an AbstractFactory
- Demo — CreditCard AbstractFactory (credit card approval system)
- Pitfalls
- Pattern comparison — AbstractFactory vs Factory
- Summary
1. Course Overview
This course covers all of the Gang of Four (GoF) creational design patterns in Java. Creational patterns focus on how objects are created, abstracting and controlling the instantiation process.
Topics covered:
- All GoF creational patterns: Singleton, Builder, Prototype, Factory Method, Abstract Factory
- When to use one pattern rather than another
- How to recognize when to apply each pattern
- Pitfalls and possible alternatives
- Examples in the Java API
- Concrete examples from the real world
Prerequisites: Basic knowledge of the Java language and a Java IDE.
2. Singleton Pattern
2.1 Introduction
The Singleton is one of the most used patterns, if not the most used, due to its simplicity of implementation and the type of problem it solves: ensuring that only one instance of a class exists in the application.
2.2 Version check
- Java: Java 21 (current LTS version)
- Build tool: Maven 3.x
- IDE: IntelliJ IDEA Ultimate (Visual Studio Code, Spring STS, Eclipse also work)
2.3 Concepts
The Singleton guarantees:
- Only one instance created of the object in the application
- control of a resource — being a creative pattern, the instantiation is entirely controlled by the implementation of the pattern
- A lazy loading — usually, although not required
Examples in Java API:
java.lang.Runtime— a single instance of the runtimejava.util.logging.Logger— depending on implementation, can be a Singleton or a Factory- Spring Beans — all Spring Beans are Singletons by default
- Graphics managers — a single instance of the graphics environment
2.4 Design features
| Characteristic | Description |
|---|---|
| Self-management | Responsible for creating yourself AND managing your life cycle (different from other creational patterns) |
| Static nature | Implemented in static mode (not necessarily a static class) |
| Thread safety | Must be thread safe to avoid creating multiple instances |
| Private instance | The instance is private (denoted by - in the UML diagram) |
| Private builder | The constructor is private — only the class itself can call it |
| No settings | The constructor generally takes no arguments |
UML diagram:
┌─────────────────────────────────────────┐
│ <<Singleton>> │
│ DbSingleton │
├─────────────────────────────────────────┤
│ - instance : DbSingleton │
├─────────────────────────────────────────┤
│ - DbSingleton() │
│ + getInstance() : DbSingleton │
└─────────────────────────────────────────┘
2.5 Everyday example — Runtime Environment
public class SingletonEverydayDemo {
public static void main(String[] args) {
// Obtenir la première instance du Runtime
Runtime rt = Runtime.getRuntime();
System.out.println(rt); // Affiche l'adresse objet, ex: java.lang.Runtime@36baf30c
// Obtenir une deuxième instance
Runtime rt2 = Runtime.getRuntime();
System.out.println(rt2); // Même adresse objet!
// Vérification : les deux références pointent vers le même objet
System.out.println(rt == rt2); // true
System.out.println(rt.equals(rt2)); // true
}
}
The two references always point to the same object — this is the Singleton guarantee. With each execution, the addresses change, but they are identical between the two variables.
2.6 Demo — Runtime Env
public class SingletonEverydayDemo {
public static void main(String[] args) {
Runtime rt = Runtime.getRuntime();
rt.gc(); // Appel de garbage collect pour prouver que c'est le vrai Runtime
System.out.println(rt); // Adresse objet, ex: java.lang.Runtime@36baf30c
Runtime rt2 = Runtime.getRuntime();
System.out.println(rt2); // Même adresse objet!
System.out.println(rt == rt2); // true
System.out.println(rt.equals(rt2)); // true
}
}
Expected output:
java.lang.Runtime@36baf30c
java.lang.Runtime@36baf30c
true
true
The
getRuntime()method (method without arguments, often namedgetInstance()in other Singletons) always returns the same instance.
2.7 Exercise — Create a Singleton
Exercise objectives:
- Create a Basic Singleton
- Demonstrate that only one instance is created
- Convert to lazy loading
- Make the Singleton thread-safe
2.8 Demo — Static Singleton (DbSingleton)
Step 1: Basic singleton (eager initialization)
package singleton;
public class DbSingleton {
// Instance unique — initialisée de façon statique (eager)
private static DbSingleton instance = new DbSingleton();
// Constructeur privé — personne d'autre ne peut instancier cette classe
private DbSingleton() {
// Initialisation si nécessaire
}
// Méthode publique d'accès à l'instance unique
public static DbSingleton getInstance() {
return instance;
}
}
User demo:
package singleton;
public class DbSingletonDemo {
public static void main(String[] args) {
// Obtenir la première instance
DbSingleton instance = DbSingleton.getInstance();
System.out.println(instance); // Affiche l'adresse objet, ex: singleton.DbSingleton@36baf30c
// Tentative avec 'new' — NE COMPILE PAS (constructeur privé)
// DbSingleton failInstance = new DbSingleton(); // ERREUR DE COMPILATION
// Obtenir une deuxième instance
DbSingleton anotherInstance = DbSingleton.getInstance();
System.out.println(anotherInstance); // Même adresse : singleton.DbSingleton@36baf30c
System.out.println(instance == anotherInstance); // true
}
}
2.9 Demo — Lazy Loaded Singleton (Initialization on Demand Holder)
The modern (since Java 5) recommended approach is the Initialization on Demand Holder idiom. It combines lazy loading and thread safety, without explicit synchronization or double-checked locking.
package singleton;
public class DbSingleton {
// Constructeur privé
private DbSingleton() {
// Initialisation si nécessaire
}
// Classe holder privée et statique — n'est créée que lorsqu'elle est accédée
private static class LazyHolder {
// L'instance est créée lors du premier accès à LazyHolder
static final DbSingleton INSTANCE = new DbSingleton();
}
// Méthode publique d'accès — thread-safe grâce à la JVM
public static DbSingleton getInstance() {
return LazyHolder.INSTANCE;
}
}
Why this approach is superior:
- Lazy loading:
LazyHolderis only loaded (and thereforeINSTANCEis created) whengetInstance()is called for the first time - Thread safety: The JVM guarantees the static initialization of classes in a thread-safe manner
- No need for
synchronized, no double-checked locking - Less error prone
Important note: If the
DbSingletonconstructor fails, aNoClassDefFoundErrorwill be raised. This behavior is the same with double-checked locking — it is an inherent risk, not unique to this idiom.
2.10 Demo — Database Integration
This demo illustrates a Singleton managing a connection to an in-memory H2 database.
pom.xml — H2 dependency:
<dependencies>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<version>2.1.210</version>
</dependency>
</dependencies>
DbSingleton with JDBC connection:
package singleton;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class DbSingleton {
// Connexion privée — initialisée une seule fois
private Connection conn = null;
private DbSingleton() {
try {
// URL de connexion à la base de données H2 en mémoire
String jdbcUrl = "jdbc:h2:mem:test";
conn = DriverManager.getConnection(jdbcUrl);
} catch (SQLException e) {
e.printStackTrace();
}
}
private static class LazyHolder {
static final DbSingleton INSTANCE = new DbSingleton();
}
public static DbSingleton getInstance() {
return LazyHolder.INSTANCE;
}
// Méthode publique pour obtenir la connexion
public Connection getConnection() {
return conn;
}
}
DbSingletonDemo — Use with SQL:
package singleton;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class DbSingletonDemo {
public static void main(String[] args) throws SQLException {
// Obtenir l'instance unique
DbSingleton instance = DbSingleton.getInstance();
System.out.println(instance);
// Obtenir la connexion via le Singleton
Connection conn = instance.getConnection();
// Créer un Statement SQL
Statement statement = conn.createStatement();
// Créer une table
statement.execute("CREATE TABLE students (id INT PRIMARY KEY, name VARCHAR(65))");
System.out.println("Table students créée.");
// Insérer un enregistrement
int rows = statement.executeUpdate("INSERT INTO students (id, name) VALUES (1, 'Bryan')");
if (rows > 0) {
System.out.println("Nouvel enregistrement inséré.");
}
// Fermer la connexion à la fin
conn.close();
}
}
Expected output:
singleton.DbSingleton@<adresse_objet>
Table students créée.
Nouvel enregistrement inséré.
Implementation summary:
- H2 dependency added in
pom.xmlprovides perfect in-memory database for testingDbSingletoncontains aprivate Connection connfield initialized in the private constructor- The
LazyHolderguarantees that the connection is only established on the first call togetInstance()getConnection()does not need to be thread-safe since access must go through theLazyHolderinstance
2.11 Pitfalls
| Trap | Explanation |
|---|---|
| Overuse | The Singleton is often applied to objects that don’t need it, slowing down the application |
| Difficult to test | No exposed interface, private constructor, private members — mocking is complicated |
| Not thread safe if poorly implemented | A naive implementation can create multiple instances in a concurrent environment |
| Transfer to Factory | As soon as getInstance() receives parameters, it is no longer a Singleton but a Factory |
| Confusion with Prototype | Calendar.getInstance() looks like a Singleton but returns a new unique instance on each call — it’s actually a Prototype |
2.12 Pattern Comparison — Singleton vs Factory
| Criterion | Singleton | Factory |
|---|---|---|
| Returned Instances | Always the same instance | Various instances, multiple types |
| Builder | One constructor, no arguments | Multiple constructors with arguments |
| Interface | Generally no interface | Driven by interface/contract |
| Testability | Difficult to test | Easy to test, mockable |
| Adaptability | Not very adaptable | Easily adapts to the environment |
Rule of thumb: If the Singleton is not suitable, examine the Factory. If
getInstance()starts accepting parameters, that’s a signal that you need a Factory.
2.13 Summary
- Use Singleton when you need to guarantee only one instance in the application
- Very simple to implement and make thread safe
- Fixes a well-defined problem but can be easily abused
- Verify that the problem really requires a Singleton before applying it
- Do not confuse with Factory — if the object must take parameters at construction, use a Factory
3. Pattern Builder
3.1 Introduction
The Builder is a pattern that developers use frequently, but rarely create themselves. It is ideal for:
- Constructing objects with many parameters
- Guarantee the immutability of the object once constructed
3.2 Concepts
| Concept | Description |
|---|---|
| Complex construction | The object to be created has many parameters or setters |
| Construction contract | Guarantees a contract on how the object is constructed |
| Forced immutability | Once construction is complete, the object can no longer be modified |
| Generics (since Java 1.5) | Ability to use generics to return different types |
Examples in Java API:
java.lang.StringBuilder— classic example of the Builder Patternjavax.xml.parsers.DocumentBuilder— illustrates use on complex objectsjava.util.Locale.Builder— various parameters for building a locale
3.3 Design features
- Fixes “telescoping constructor” issue — avoid creating a constructor for each parameter combination
- Static inner class — the Builder is typically implemented as a static inner class
- Returns the instance of the object it builds — via the
build()method - Eliminate exposed setters — JavaBeans anti-pattern
- No need for multiple constructors — the Builder can choose the right constructor depending on its state
3.4 Everyday example — StringBuilder
public class BuilderEverydayDemo {
public static void main(String[] args) {
// Utilisation du StringBuilder — exemple classique du Builder Pattern
StringBuilder builder = new StringBuilder();
builder.append("Learning the ");
builder.append("Builder Pattern");
builder.append(" with Java ");
builder.append(21); // Accepte différents types
// Le Builder crée l'objet final via toString()
String result = builder.toString();
System.out.println(result);
// Sortie: Learning the Builder Pattern with Java 21
}
}
Before the
StringBuilder(Java 1.5), we used the+orStringBufferoperator, creating multiple immutable String objects with each concatenation — a very expensive operation. TheStringBuildersolves this problem thanks to the Builder pattern.
3.5 Demo — StringBuilder
public class BuilderEverydayDemo {
public static void main(String[] args) {
StringBuilder builder = new StringBuilder();
builder.append("Hello");
builder.append(", ");
builder.append("World");
builder.append("! Answer: ");
builder.append(42); // Appende un int
// Méthode build() équivalente : toString()
String builderString = builder.toString();
System.out.println(builderString);
// Sortie: Hello, World! Answer: 42
}
}
The
StringBuilderwas retroactively added to the Java API to refactor theStringclass — an excellent illustration of how the Builder Pattern can be retrofitted into existing code.
3.6 Exercise — Create a Builder
Steps:
- Demonstrate exposed setters of a JavaBean (immutability issue)
- Illustrate the disadvantages of telescoping constructors
- Create our own Builder Pattern
- Complete the example with dot notation
3.7 Demo — Immutability issue with setters (JavaBean)
Type enumerations:
package builder;
public enum HardDriveSize {
DEFAULT, MAX
}
package builder;
public enum RAM {
DEFAULT, UPGRADED
}
ComputerBean — Classic JavaBean (NOT immutable):
package builder;
public class Computer {
private HardDriveSize diskSize;
private RAM ramSize;
private boolean hasUsbc;
private boolean hasGigabitWifi;
// Constructeur no-args
public Computer() {}
// Getters
public HardDriveSize getDiskSize() { return diskSize; }
public RAM getRamSize() { return ramSize; }
public boolean isHasUsbc() { return hasUsbc; }
public boolean isHasGigabitWifi() { return hasGigabitWifi; }
// Setters — problème : l'objet n'est PAS immutable
public void setDiskSize(HardDriveSize diskSize) { this.diskSize = diskSize; }
public void setRamSize(RAM ramSize) { this.ramSize = ramSize; }
public void setHasUsbc(boolean hasUsbc) { this.hasUsbc = hasUsbc; }
public void setHasGigabitWifi(boolean hasGigabitWifi) { this.hasGigabitWifi = hasGigabitWifi; }
}
Problem Demo:
public class ComputerDemo {
public static void main(String[] args) {
Computer computer = new Computer();
computer.setDiskSize(HardDriveSize.DEFAULT);
computer.setRamSize(RAM.DEFAULT);
computer.setHasUsbc(true);
computer.setHasGigabitWifi(true);
System.out.println(computer.getDiskSize()); // DEFAULT
System.out.println(computer.getRamSize()); // DEFAULT
System.out.println(computer.isHasUsbc()); // true
System.out.println(computer.isHasGigabitWifi()); // true
// PROBLÈME : On peut modifier l'objet APRÈS création
computer.setDiskSize(HardDriveSize.MAX); // Pas de contrat!
// On peut aussi omettre des champs critiques sans erreur de compilation
}
}
Identified issues:
- No immutability: the object can be modified after construction
- No contract: mandatory fields can be omitted without error
3.8 Demo — Telescoping Builders
package builder;
public class ComputerTelescoping {
private HardDriveSize diskSize;
private RAM ramSize;
private boolean hasUsbc;
private boolean hasGigabitWifi;
// Constructeur avec seulement diskSize
public ComputerTelescoping(HardDriveSize diskSize) {
this(diskSize, RAM.DEFAULT);
}
// Constructeur avec diskSize et ramSize
public ComputerTelescoping(HardDriveSize diskSize, RAM ramSize) {
this(diskSize, ramSize, false);
}
// Constructeur avec diskSize, ramSize et hasUsbc
public ComputerTelescoping(HardDriveSize diskSize, RAM ramSize, boolean hasUsbc) {
this(diskSize, ramSize, hasUsbc, false);
}
// Constructeur complet
public ComputerTelescoping(HardDriveSize diskSize, RAM ramSize,
boolean hasUsbc, boolean hasGigabitWifi) {
this.diskSize = diskSize;
this.ramSize = ramSize;
this.hasUsbc = hasUsbc;
this.hasGigabitWifi = hasGigabitWifi;
}
// Getters seulement — immutable
public HardDriveSize getDiskSize() { return diskSize; }
public RAM getRamSize() { return ramSize; }
public boolean isHasUsbc() { return hasUsbc; }
public boolean isHasGigabitWifi() { return hasGigabitWifi; }
}
Demo:
public class ComputerTelescopingDemo {
public static void main(String[] args) {
// Utilisation du constructeur complet avec tous les defaults
ComputerTelescoping computer =
new ComputerTelescoping(HardDriveSize.DEFAULT, RAM.DEFAULT, true, true);
System.out.println(computer.getDiskSize()); // DEFAULT
System.out.println(computer.getRamSize()); // DEFAULT
System.out.println(computer.isHasUsbc()); // true
System.out.println(computer.isHasGigabitWifi()); // true
}
}
Problem resolved: Immutability guaranteed, contract respected. New problem: If we add a Bluetooth option, how can we manage all the possible combinations without multiplying manufacturers? This is exactly where the Builder comes in.
3.9 Demo — Builder Pattern (ComputerBuilder)
Computer with integrated Builder:
package builder;
public class Computer {
// Champs de l'objet final (sans setters — immutable)
private HardDriveSize diskSize;
private RAM ramSize;
private boolean hasUsbc;
private boolean hasGigabitWifi;
// Constructeur privé qui prend un Builder
private Computer(ComputerBuilder builder) {
this.diskSize = builder.diskSize;
this.ramSize = builder.ramSize;
this.hasUsbc = builder.hasUsbc;
this.hasGigabitWifi = builder.hasGigabitWifi;
}
// Getters seulement — immutabilité totale
public HardDriveSize getDiskSize() { return diskSize; }
public RAM getRamSize() { return ramSize; }
public boolean isHasUsbc() { return hasUsbc; }
public boolean isHasGigabitWifi() { return hasGigabitWifi; }
@Override
public String toString() {
return "Computer{diskSize=" + diskSize
+ ", ramSize=" + ramSize
+ ", hasUsbc=" + hasUsbc
+ ", hasGigabitWifi=" + hasGigabitWifi + "}";
}
// =========================================================
// Classe Builder interne statique
// =========================================================
public static class ComputerBuilder {
// Duplication des champs — représentation de tout ce qui peut être configuré
private HardDriveSize diskSize;
private RAM ramSize;
private boolean hasUsbc;
private boolean hasGigabitWifi;
// Constructeur no-args du Builder
public ComputerBuilder() {}
// Méthodes de configuration — retournent 'this' pour permettre le chaînage
public ComputerBuilder addHdd(HardDriveSize diskSize) {
this.diskSize = diskSize;
return this;
}
public ComputerBuilder addRam(RAM ramSize) {
this.ramSize = ramSize;
return this;
}
public ComputerBuilder hasUsbc(boolean hasUsbc) {
this.hasUsbc = hasUsbc;
return this;
}
public ComputerBuilder hasGigabitWifi(boolean hasGigabitWifi) {
this.hasGigabitWifi = hasGigabitWifi;
return this;
}
// Méthode build() — crée et retourne l'objet Computer final
public Computer build() {
return new Computer(this);
}
}
}
User demo — Dot notation (chaining):
public class BuilderDemo {
public static void main(String[] args) {
// Création du Builder et configuration par chaînage
Computer.ComputerBuilder builder = new Computer.ComputerBuilder()
.addHdd(HardDriveSize.MAX)
.addRam(RAM.UPGRADED)
.hasUsbc(true)
.hasGigabitWifi(true);
// Construction de l'objet final — immutable
Computer computer = builder.build();
System.out.println(computer.getDiskSize()); // MAX
System.out.println(computer.getRamSize()); // UPGRADED
System.out.println(computer.isHasUsbc()); // true
System.out.println(computer.isHasGigabitWifi()); // true
// Pas de setter — l'objet ne peut pas être modifié après build()
// computer.setDiskSize(...) // N'EXISTE PAS — ERREUR DE COMPILATION
// Autre configuration possible sans nouveau constructeur
Computer minimalComputer = new Computer.ComputerBuilder()
.addHdd(HardDriveSize.DEFAULT)
.build(); // Seul le disque dur est configuré
}
}
Advantages of the Builder:
- Immutability guaranteed after
build()- Flexibility: any combination of parameters without multiplying constructors
- Contract: possibility of adding validations in
build()or configuration methods- Readability: the dot notation makes the code very expressive
3.10 Pitfalls
| Trap | Explanation |
|---|---|
| Additional complexity | Light overhead compared to a simple constructor |
Return this | Unusual concept — developers are not always familiar with the fact that a method returns the current instance |
| Duplication of fields | Fields are duplicated between the main class and the Builder |
| Generally not refactored | Unlike the Prototype, the Builder is better to plan from the beginning |
These points are not really major pitfalls — they are more specifics to be aware of. The Builder has almost no real downsides.
3.11 Pattern Comparison — Builder vs Prototype
| Criterion | Builder | Prototype |
|---|---|---|
| Objective | Handle complex constructors with many parameters | Avoiding expensive constructors via copying |
| Interface | Usually not necessary (but possible) | Implements Cloneable |
| Implementation | Often in the class itself (inner class), can be separated | In the class to clone |
| Legacy integration | Easy — can be a separate class | Difficult — must be in class |
Keyword new | Used normally | Averted after first creation |
3.12 Summary
- The Builder is a creative way to manage the complexity of constructors
- Almost as easy to implement as the Singleton
- Very few contraindications
- Can be refactored with a separate class (contrary to popular belief)
- Guarantees both immutability and configuration flexibility
4. Pattern Prototype
4.1 Introduction
Prototype is used when the type of object to be created is determined by a prototypical instance, which is cloned to produce new instances. It is often used to obtain unique instances of the same object repeatedly at low cost.
4.2 Concepts
| Concept | Description |
|---|---|
| Avoid costly creation | Cloning an existing object is cheaper than creating a new one |
| Avoid subclassing | We create an instance of the prototype itself |
No new | The first instance uses new, but subsequent ones are cloned |
| Recommended interface | Typically an interface is created for the prototype |
| Registry | Prototypes are stored in a registry, cloned on demand |
Example in Java API:
java.lang.Object#clone()— base method of any prototype in Java
4.3 Design features
- Change how to call
new— if the object is expensive to create but one copy is enough - Implements
Cloneableand theclone()method — avoids the keywordnew - Instances always unique — even if cloned (check for references)
- The client does not manage expensive creation — unlike the Builder
- Shallow copy vs Deep copy:
- Shallow copy: copies primitive properties and references (referenced objects are not duplicated)
- Deep copy: also copies the referenced objects (each instance is completely independent)
4.4 Everyday example — Statement (shallow copy)
package prototype.everyday;
import java.util.ArrayList;
public class Statement implements Cloneable {
private String sql;
private ArrayList<String> parameters;
private Record record;
// Constructeur coûteux — imagine ici une connexion DB, une compilation, etc.
public Statement(String sql, ArrayList<String> parameters, Record record) {
this.sql = sql;
this.parameters = parameters;
this.record = record;
}
// Méthode clone — shallow copy par défaut
@Override
public Statement clone() throws CloneNotSupportedException {
return (Statement) super.clone();
}
// Getters
public String getSql() { return sql; }
public ArrayList<String> getParameters() { return parameters; }
public Record getRecord() { return record; }
public void setRecord(Record record) { this.record = record; }
}
package prototype.everyday;
// Objet Record — simple classe pour illustrer les références
public class Record implements Cloneable {
@Override
public Record clone() throws CloneNotSupportedException {
return (Record) super.clone();
}
}
4.5 Demo — Statement (shallow copy vs deep copy)
package prototype.everyday;
import java.util.ArrayList;
public class StatementDemo {
public static void main(String[] args) throws CloneNotSupportedException {
String sql = "select * from movies where title = ?";
ArrayList<String> parameters = new ArrayList<>();
parameters.add("Star Wars");
Record record = new Record();
// Créer la première instance (opération coûteuse)
Statement stmt1 = new Statement(sql, parameters, record);
System.out.println("SQL: " + stmt1.getSql());
System.out.println("Paramètres: " + stmt1.getParameters());
System.out.println("Record (adresse): " + stmt1.getRecord()); // ex: Record@24d46ca6
// === SHALLOW COPY ===
// Cloner le Statement (opération peu coûteuse)
Statement stmt2 = stmt1.clone();
System.out.println("\nAfter shallow clone:");
System.out.println("SQL: " + stmt2.getSql());
System.out.println("Paramètres: " + stmt2.getParameters());
// ATTENTION : même adresse pour le Record — shallow copy!
System.out.println("Record (adresse): " + stmt2.getRecord()); // Même adresse!
// === DEEP COPY ===
// Pour une deep copy, il faut cloner aussi les objets référencés
Statement stmt3 = stmt1.clone();
stmt3.setRecord(stmt1.getRecord().clone()); // Clone le Record également
System.out.println("\nAfter deep clone:");
System.out.println("Record (adresse): " + stmt3.getRecord()); // Adresse DIFFÉRENTE!
}
}
Output (shallow copy):
Record (adresse): prototype.everyday.Record@24d46ca6
Record (adresse): prototype.everyday.Record@24d46ca6 ← MÊME adresse
Output (deep copy):
Record (adresse): prototype.everyday.Record@24d46ca6
Record (adresse): prototype.everyday.Record@4517d9a3 ← DIFFÉRENTE adresse
For a deep copy, each referenced object must also implement
Cloneableand itsclone()method, then be explicitly cloned into the parent object’sclone()method.
4.6 Exercise — Create a Prototype
Objectives:
- Create a Complete Prototype Pattern
- Demonstrate shallow copy
- Create an object with Registry
4.7 Demo — Prototype with Registry (inventory system)
Item abstract class (prototypical base):
package prototype;
public abstract class Item implements Cloneable {
private String title;
private double price;
private String url;
// Implémentation de clone — shallow copy par défaut
// La sous-classe peut la surcharger pour une deep copy
@Override
public Item clone() throws CloneNotSupportedException {
return (Item) super.clone();
}
// Getters et Setters
public String getTitle() { return title; }
public void setTitle(String title) { this.title = title; }
public double getPrice() { return price; }
public void setPrice(double price) { this.price = price; }
public String getUrl() { return url; }
public void setUrl(String url) { this.url = url; }
@Override
public String toString() {
return "Item{title='" + title + "', price=" + price + ", url='" + url + "'}";
}
}
Movie subclass:
package prototype;
public class Movie extends Item {
private int runtime; // en minutes
public int getRuntime() { return runtime; }
public void setRuntime(int runtime) { this.runtime = runtime; }
@Override
public String toString() {
return "Movie{title='" + getTitle() + "', runtime=" + runtime
+ ", price=" + getPrice() + ", url='" + getUrl() + "'}";
}
}
Book subclass:
package prototype;
public class Book extends Item {
private int numberOfPages;
public int getNumberOfPages() { return numberOfPages; }
public void setNumberOfPages(int numberOfPages) { this.numberOfPages = numberOfPages; }
@Override
public String toString() {
return "Book{title='" + getTitle() + "', pages=" + numberOfPages
+ ", price=" + getPrice() + ", url='" + getUrl() + "'}";
}
}
Registry — core of the Prototype Pattern:
package prototype;
import java.util.HashMap;
import java.util.Map;
public class Registry {
// HashMap servant de registre des prototypes
private Map<String, Item> items = new HashMap<>();
public Registry() {
// Charger les prototypes par défaut au démarrage
loadItems();
}
// Méthode factory — clone le prototype correspondant au type demandé
public Item createItem(String type) {
Item item = null;
try {
// Obtenir le prototype du registre et le cloner
item = (Item) items.get(type).clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
return item;
}
// Initialisation des prototypes par défaut
private void loadItems() {
// Prototype Movie avec valeurs par défaut
Movie movie = new Movie();
movie.setTitle("Default Movie");
movie.setRuntime(0);
movie.setPrice(24.99);
movie.setUrl("http://www.imdb.com");
items.put("Movie", movie);
// Prototype Book avec valeurs par défaut
Book book = new Book();
book.setTitle("Default Book");
book.setNumberOfPages(0);
book.setPrice(19.99);
book.setUrl("http://www.amazon.com");
items.put("Book", book);
}
}
PrototypeDemo — Using the Registry:
package prototype;
public class PrototypeDemo {
public static void main(String[] args) {
Registry registry = new Registry();
// Créer un Movie — cloner le prototype, personnaliser
Movie movie1 = (Movie) registry.createItem("Movie");
movie1.setTitle("Star Wars");
System.out.println(movie1); // Movie{title='Star Wars', runtime=0, price=24.99, ...}
System.out.println("Adresse: " + movie1); // Adresse unique
// Créer un autre Movie — autre clone indépendant
Movie movie2 = (Movie) registry.createItem("Movie");
movie2.setTitle("The Empire Strikes Back");
System.out.println(movie2); // Movie{title='The Empire Strikes Back', ...}
System.out.println("Adresse: " + movie2); // Adresse DIFFÉRENTE de movie1
// Chaque clone est indépendant — pas de keyword 'new' dans Registry.createItem()
System.out.println("Même objet? " + (movie1 == movie2)); // false
}
}
Key points:
- Registry stores prototypes with default values (
loadItems())createItem()makes a clone of the prototype — nonewoutside ofloadItems()- Each cloned instance is unique and can be customized independently
- Ideal for systems like Amazon/Netflix where thousands of Item objects are created
Limitation of the
CloneableJava interface:
- It does not use generics (created in Java 1.0) — requires a cast
- The
clone()method throws aCloneNotSupportedException— requires a try-catch- These disadvantages lead some to create their own Cloneable interface with generics
4.8 Pitfalls
| Trap | Explanation |
|---|---|
| Often not used | Unlike the Singleton, the Prototype is often unknown |
| Often combined with another pattern | The Registry is almost always necessary — which makes it similar to a framework |
| Deep copy necessary but not automatic | The Cloneable interface only does shallow copy by default |
| Questioning | If the deep copy becomes as complex as the initial construction, the gain is zero |
4.9 Pattern Comparison — Prototype vs Factory
| Criterion | Prototype | Factory |
|---|---|---|
| Focus | Lightweight construction via clone | Flexible instantiation depending on type |
| Method | clone() | Multiple constructors / new |
| Returned type | Copy of oneself | Concrete instances varied according to parameters |
| Default values | Inherited from the original prototype | No programmed defaults |
Keyword new | Avoided (unless initialized) | Used — every instance is fresh |
4.10 Summary
- The Prototype guarantees a unique instance for each request
- Often a refactoring pattern — introduced when performance issues emerge
- Do not systematically choose the Factory - the Prototype may be better suited
- Ideal for systems creating many similar objects (inventory, cache, etc.)
5. Pattern Factory Method
5.1 Introduction
The Factory Method is, in a way, the opposite of the Singleton. It is probably the second most used creative pattern. It delegates creation logic to subclasses.
5.2 Concepts
| Concept | Description |
|---|---|
| Does not expose instantiation logic | Client does not know which concrete type is created |
| Delegation to subclasses | Creation is delegated to subclasses |
| Common interface | The client interacts only via a common interface |
| Architecturally driven | Often implemented by an architectural framework |
Examples in Java API:
java.util.Calendar.getInstance()— returns aGregorianCalendar(or another subclass)java.util.ResourceBundle.getBundle()java.text.NumberFormat.getInstance()
Calendaris a static factory — the client does not know which subclass ofCalendaris returned.
5.3 Design features
- Almost the opposite of Singleton — instead of a single instance, Factory creates many varied instances
- Responsible for creation and lifecycle — on the creation side only, not like the Singleton which also manages the lifecycle
- Objects created and referenced via a common interface
- Multiple concrete class references — client is unaware of details
- Parameterized request method — parameters determine concrete type
UML diagram:
┌─────────────────────┐
│ Website │ ← Classe abstraite / interface
│─────────────────────│
│ + createWebsite() │ ← Factory Method
└──────────┬──────────┘
│ implements
┌──────┴──────┐
│ │
┌──▼──┐ ┌──▼──┐
│Blog │ │Shop │ ← Implémentations concrètes
└─────┘ └─────┘
┌──────────────────────┐
│ WebsiteFactory │ ← Factory
│──────────────────────│
│ + getWebsite(type) │ ← Retourne Website
└──────────────────────┘
5.4 Daily example — Calendar
import java.util.Calendar;
public class CalendarDemo {
public static void main(String[] args) {
// Factory Method — on ne sait pas quelle sous-classe est retournée
Calendar cal = Calendar.getInstance();
// Affiche le type concret : java.util.GregorianCalendar
System.out.println(cal);
System.out.println(cal.getClass()); // class java.util.GregorianCalendar
// Utilisation — le client travaille avec l'interface Calendar
System.out.println(cal.get(Calendar.DAY_OF_MONTH));
}
}
5.5 Demo — Calendar
import java.util.Calendar;
import java.util.Locale;
import java.util.TimeZone;
public class CalendarEverydayDemo {
public static void main(String[] args) {
// Factory Method par défaut
Calendar cal1 = Calendar.getInstance();
System.out.println(cal1.getClass());
// Sortie: class java.util.GregorianCalendar
// Factory Method avec TimeZone — peut retourner un type différent
Calendar cal2 = Calendar.getInstance(TimeZone.getTimeZone("America/New_York"));
System.out.println(cal2.getClass());
// Factory Method avec Locale
Calendar cal3 = Calendar.getInstance(Locale.JAPAN);
System.out.println(cal3.getClass());
// Sortie: class sun.util.BuddhistCalendar (sur certaines JVM)
System.out.println("Jour du mois: " + cal1.get(Calendar.DAY_OF_MONTH));
}
}
Factory Method key: Parameters passed to
getInstance()allow the Factory to return the correct concrete implementation — while remaining transparent to the client.
5.6 Exercise — Create a Factory
Objectives:
- Create basic pages (templates)
- Create a website from pages
- Create concrete classes (Blog, Shop)
- Create the Factory and WebsiteType enum
5.7 Demo — Website Factory (website builder)
Base pages (concrete classes):
package factory;
public abstract class Page {
// Classe de base pour toutes les pages
@Override
public String toString() {
return this.getClass().getSimpleName();
}
}
package factory;
public class PostPage extends Page {}
public class AboutPage extends Page {}
public class CommentPage extends Page {}
public class ContactPage extends Page {}
public class CartPage extends Page {}
public class ItemPage extends Page {}
public class SearchPage extends Page {}
Website abstract class (with Factory Method):
package factory;
import java.util.ArrayList;
import java.util.List;
public abstract class Website {
protected List<Page> pages = new ArrayList<>();
// Constructeur no-args — appelle automatiquement le Factory Method
public Website() {
this.createWebsite();
}
// Factory Method — délégué aux sous-classes
public abstract void createWebsite();
public List<Page> getPages() {
return pages;
}
}
Concrete implementations:
package factory;
// Classe concrète : Blog — surcharge le Factory Method
public class Blog extends Website {
@Override
public void createWebsite() {
pages.add(new PostPage());
pages.add(new AboutPage());
pages.add(new CommentPage());
pages.add(new ContactPage());
}
}
package factory;
// Classe concrète : Shop — surcharge le Factory Method
public class Shop extends Website {
@Override
public void createWebsite() {
pages.add(new CartPage());
pages.add(new ItemPage());
pages.add(new SearchPage());
}
}
WebsiteFactory — The Factory:
package factory;
public class WebsiteFactory {
public static Website getWebsite(WebsiteType siteType) {
switch (siteType) {
case BLOG:
return new Blog();
case SHOP:
return new Shop();
default:
throw new UnsupportedOperationException("Type de site non supporté: " + siteType);
}
}
}
FactoryDemo — Usage:
package factory;
public class FactoryDemo {
public static void main(String[] args) {
// Créer un Blog via le Factory
Website blog = WebsiteFactory.getWebsite(WebsiteType.BLOG);
System.out.println("Pages du Blog:");
blog.getPages().forEach(System.out::println);
// Sortie: PostPage, AboutPage, CommentPage, ContactPage
System.out.println();
// Créer un Shop via le Factory
Website shop = WebsiteFactory.getWebsite(WebsiteType.SHOP);
System.out.println("Pages du Shop:");
shop.getPages().forEach(System.out::println);
// Sortie: CartPage, ItemPage, SearchPage
}
}
Factory Method architecture:
Website— abstract class with Factory MethodcreateWebsite()BlogandShop— implementcreateWebsite()with their own pagesWebsiteFactory— returns the correct implementation depending on the requested typeFactoryDemo— client only works withWebsiteandWebsiteFactory
5.8 Demo — Replacing string literals with an Enum
package factory;
public enum WebsiteType {
BLOG,
SHOP
}
Factory modified with Enum:
package factory;
public class WebsiteFactory {
// Utilisation de l'Enum au lieu des String literals
public static Website getWebsite(WebsiteType siteType) {
switch (siteType) {
case BLOG:
return new Blog();
case SHOP:
return new Shop();
default:
throw new UnsupportedOperationException("Type non supporté: " + siteType);
}
}
}
Modified demo:
package factory;
public class FactoryDemo {
public static void main(String[] args) {
// Utilisation de l'Enum — plus de String literals!
Website blog = WebsiteFactory.getWebsite(WebsiteType.BLOG);
Website shop = WebsiteFactory.getWebsite(WebsiteType.SHOP);
System.out.println("Blog: " + blog.getPages());
System.out.println("Shop: " + shop.getPages());
}
}
Advantage of Enum: Eliminates fragile String literals in the code, makes everything more predictable for unit testing and quality control.
5.9 Pitfalls
| Trap | Explanation |
|---|---|
| Complexity | The Factory is almost double the code of other creational patterns |
| Common error | Creation does not take place in the Factory itself, but in subclasses |
| Not refactored | The Factory usually needs to be planned from the beginning — difficult to introduce after the fact |
5.10 Pattern comparison — Factory vs Singleton
| Criterion | Singleton | Factory |
|---|---|---|
| Instances | The same every time | Varied according to parameters |
| Builders | One, without arguments | Multiples with arguments |
| Interface | Usually none | Driven by interface/contract |
| Subclasses | None | Still subclasses |
| Adaptability | Low | High — adapts to the environment |
5.11 Summary
- The Factory Method is parameter-driven — the only truly parametric creative pattern
- Resolves complex creation differently from the Builder (the Builder manages the construction parameters, the Factory manages the choice of type at runtime)
- Is the opposite of Singleton — if Singleton doesn’t fit, look at Factory
- Has a cousin: the Abstract Factory (next module)
6. Pattern Abstract Factory
6.1 Introduction
The Abstract Factory is very similar to the Factory Method and is actually a Factory of Factories. It is the most complex of creational patterns.
6.2 Concepts
| Concept | Description |
|---|---|
| Factory of factories | The AbstractFactory produces factories, which produce objects |
| Related objects | Brings together factories for related/family objects |
| Common interface | The interface is carried through the AbstractFactory and the underlying factories |
| Delegation | Like the Factory Method, creation is delegated to subclasses |
Example in Java API:
javax.xml.parsers.DocumentBuilderFactory— the AbstractFactoryDocumentBuilder— the FactoryDocument— the concrete object
Few examples in the standard Java API because this pattern is mainly used in frameworks.
6.3 Design features
- Group factories — when you want to group linked factories
- The factory, not the AbstractFactory, manages the lifecycle
- Common interface carried across the entire hierarchy
- Concrete classes returned by the underlying factory
- Parameterized creation method — just like the Factory Method
- Built by composition — uses object composition
UML diagram:
┌──────────────────────────────────┐
│ CreditCardFactory │ ← AbstractFactory
│──────────────────────────────────│
│ + getCreditCardFactory(score) │
└────────────────┬─────────────────┘
│
┌────────┴────────┐
│ │
┌───────▼──────┐ ┌──────▼────────┐
│ AmexFactory │ │ VisaFactory │ ← Factories concrètes
│──────────────│ │───────────────│
│ + getCard() │ │ + getCard() │
│ + getValid() │ │ + getValid() │
└──────────────┘ └───────────────┘
│ │
AmexGold/Platinum VisaGold/Platinum ← Classes concrètes finales
6.4 Everyday example — DocumentBuilderFactory
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import java.io.ByteArrayInputStream;
public class DocumentBuilderDemo {
public static void main(String[] args) throws Exception {
String xml = "<document><element>Hello Abstract Factory</element></document>";
// Convertir le XML en stream
ByteArrayInputStream bais = new ByteArrayInputStream(xml.getBytes());
// AbstractFactory : DocumentBuilderFactory
DocumentBuilderFactory abstractFactory = DocumentBuilderFactory.newInstance();
// Factory : DocumentBuilder
DocumentBuilder factory = abstractFactory.newDocumentBuilder();
// Objet concret : Document
Document document = factory.parse(bais);
// Utilisation du document
Element root = document.getDocumentElement();
System.out.println("Root element: " + root.getTagName()); // document
// Afficher les types concrets réels
System.out.println("AbstractFactory type: " + abstractFactory.getClass());
// Sortie: com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl
System.out.println("Factory type: " + factory.getClass());
// Sortie: com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderImpl
}
}
AbstractFactory key: The client does not know the implementation of
DocumentBuilderFactorynor that ofDocumentBuilder— it only knows that it is getting a workingDocument. The underlying implementation (Apache Xerces) can be swapped via command line parameters.
6.5 Demo — DocumentBuilderFactory
The previous example illustrates the three levels:
- AbstractFactory:
DocumentBuilderFactory— it is unclear which implementation is used - Factory:
DocumentBuilder— the concrete factory that produces Documents - Concrete object:
Document— the final object created
Execution result:
Root element: document
AbstractFactory type: class com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl
Factory type: class com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderImpl
6.6 Exercise — Create an AbstractFactory
Objectives:
- Browse the provided scaffolding code
- Construct the AbstractFactory (
CreditCardFactory) - Build the underlying factories (
AmexFactory,VisaFactory) - Produce the final product and run the demo
6.7 Demo — CreditCard AbstractFactory (credit card approval system)
Interfaces and base classes:
package abstractfactory;
// Interface CreditCard — contrat commun pour toutes les cartes
public interface CreditCard {
String getCardType();
// Autres méthodes communes...
}
package abstractfactory;
// Interface Validator — contrat de validation
public interface Validator {
boolean validate(String cardNumber, String cvv);
}
package abstractfactory;
// Types de cartes (Enum)
public enum CardType {
GOLD, PLATINUM
}
Concrete implementations of American Express cards:
package abstractfactory;
public class AmexGoldCreditCard implements CreditCard {
@Override
public String getCardType() {
return "American Express Gold";
}
}
package abstractfactory;
public class AmexPlatinumCreditCard implements CreditCard {
@Override
public String getCardType() {
return "American Express Platinum";
}
}
Concrete implementations of Visa cards:
package abstractfactory;
public class VisaGoldCreditCard implements CreditCard {
@Override
public String getCardType() {
return "Visa Gold";
}
}
package abstractfactory;
public class VisaPlatinumCreditCard implements CreditCard {
@Override
public String getCardType() {
return "Visa Platinum";
}
}
Validators (stubbed for the example):
package abstractfactory;
public class AmexGoldValidator implements Validator {
@Override
public boolean validate(String cardNumber, String cvv) {
// Logique de validation pour la carte Amex Gold
return true; // Stubbed
}
}
public class AmexPlatinumValidator implements Validator {
@Override
public boolean validate(String cardNumber, String cvv) {
return true; // Stubbed
}
}
public class VisaValidator implements Validator {
@Override
public boolean validate(String cardNumber, String cvv) {
return true; // Stubbed
}
}
AbstractFactory — CreditCardFactory:
package abstractfactory;
// AbstractFactory — retourne une factory selon le credit score
public abstract class CreditCardFactory {
// Méthodes abstraites du Factory Method sous-jacent
public abstract CreditCard getCreditCard(CardType cardType);
public abstract Validator getValidator(CardType cardType);
// Méthode statique de l'AbstractFactory
// Retourne la factory appropriée selon le credit score
public static CreditCardFactory getCreditCardFactory(int creditScore) {
if (creditScore > 650) {
return new AmexFactory();
} else {
return new VisaFactory();
}
}
}
Concrete Factory AmexFactory:
package abstractfactory;
public class AmexFactory extends CreditCardFactory {
@Override
public CreditCard getCreditCard(CardType cardType) {
switch (cardType) {
case GOLD:
return new AmexGoldCreditCard();
case PLATINUM:
return new AmexPlatinumCreditCard();
default:
throw new UnsupportedOperationException("Type de carte non supporté");
}
}
@Override
public Validator getValidator(CardType cardType) {
switch (cardType) {
case GOLD:
return new AmexGoldValidator();
case PLATINUM:
return new AmexPlatinumValidator();
default:
throw new UnsupportedOperationException("Type de carte non supporté");
}
}
}
Concrete Factory VisaFactory:
package abstractfactory;
public class VisaFactory extends CreditCardFactory {
@Override
public CreditCard getCreditCard(CardType cardType) {
switch (cardType) {
case GOLD:
return new VisaGoldCreditCard();
case PLATINUM:
return new VisaPlatinumCreditCard();
default:
throw new UnsupportedOperationException("Type de carte non supporté");
}
}
@Override
public Validator getValidator(CardType cardType) {
// Un seul validator pour toutes les cartes Visa dans cet exemple
return new VisaValidator();
}
}
AbstractFactoryDemo — Usage:
package abstractfactory;
public class AbstractFactoryDemo {
public static void main(String[] args) {
// Scénario 1 : Credit score élevé → American Express
CreditCardFactory factory1 = CreditCardFactory.getCreditCardFactory(775);
CreditCard card1 = factory1.getCreditCard(CardType.PLATINUM);
Validator validator1 = factory1.getValidator(CardType.PLATINUM);
System.out.println("Credit score 775 → " + card1.getCardType());
// Sortie: Credit score 775 → American Express Platinum
// Scénario 2 : Credit score faible → Visa
CreditCardFactory factory2 = CreditCardFactory.getCreditCardFactory(600);
CreditCard card2 = factory2.getCreditCard(CardType.GOLD);
Validator validator2 = factory2.getValidator(CardType.GOLD);
System.out.println("Credit score 600 → " + card2.getCardType());
// Sortie: Credit score 600 → Visa Gold
// Note : Le client ne mentionne jamais "AmexFactory" ou "VisaFactory"
// Il travaille uniquement avec CreditCardFactory, CreditCard et Validator
}
}
Output:
Credit score 775 → American Express Platinum
Credit score 600 → Visa Gold
Architecture of the AbstractFactory:
CreditCardFactory(AbstractFactory) — returns the correct factory according to the credit scoreAmexFactory/VisaFactory(Concrete Factories) — return cards and validators according to typeAmexGoldCreditCard,VisaPlatinumCreditCard, etc. (Final concrete objects)- Client only knows
CreditCardFactory,CreditCardandValidator— total abstraction
Real example: This type of architecture has been used in real financial systems where criteria (credit score, region, etc.) determine which family of financial products to offer to the customer.
6.8 Pitfalls
| Trap | Explanation |
|---|---|
| High complexity | The most complex of creational patterns |
| Switch at runtime | There is a point where a switch/if is necessary — the client has a slight influence |
| Contains other patterns | Borders on the framework — includes the Factory Method and potentially other patterns |
| Very specific | Other patterns solve larger problems; the AbstractFactory is very targeted |
| Often refactored from Factory | Often starts as a Factory, then evolves into AbstractFactory |
6.9 Pattern comparison — AbstractFactory vs Factory
| Criterion | Factory Method | Abstract Factory |
|---|---|---|
| Instances | Varied | Varied (but organized in families) |
| Interface | Interface driven | Interface-driven (two levels) |
| Subclasses | Yes | Yes (two levels) |
| Adaptability | High | High |
| Underlying Factory | No — is the factory | Yes — contains one or more factories |
| Hidden concrete factory | No | Yes — the AbstractFactory hides the concrete factory |
| Composition | Usually inheritance | Generally composition |
| Complexity | Moderate | High |
6.10 Summary
- The AbstractFactory is a group of similar factories
- This is the most complex of the creation patterns
- Highly abstract — uses interfaces, subclasses, contracts and other patterns
- Typically a framework pattern — especially useful for complex creation scenarios requiring multiple cases
- This pattern concludes the creational patterns — the next groups are the Behavioral and Structural patterns
7. Summary table of creational patterns
| Pattern | Problem solved | Instances | Settings | Interface | Complexity |
|---|---|---|---|---|---|
| Singleton | Guarantee a single instance | 1 always | None | No | Low |
| Builder | Complex construction, immutability | News | Via configuration methods | Optional | Low-Moderate |
| Prototype | Avoid costly creation | Unique clones | Via clone/registry | Cloneable | Moderate |
| Factory Method | Delegate creation to subclasses | Varied depending on type | Yes (type) | Required | Moderate-High |
| Abstract Factory | Group linked factories | Object families | Yes (family + type) | Required (2 levels) | High |
When to use which pattern?
Besoin d'une seule instance ?
└─→ Singleton
Construction avec nombreux paramètres / immutabilité ?
└─→ Builder
Création coûteuse, copie suffisante ?
└─→ Prototype
Créer différents types via une interface commune ?
└─→ Factory Method
Grouper plusieurs Factory Methods reliés ?
└─→ Abstract Factory
Complexity hierarchy
Singleton < Builder < Prototype < Factory Method < Abstract Factory
(plus simple) (plus complexe)
Real-world examples in the Java API
| Java API | Pattern |
|---|---|
Runtime.getRuntime() | Singleton |
StringBuilder | Builder |
Locale.Builder | Builder |
java.lang.Object#clone() | Prototype (basic) |
Calendar.getInstance() | Factory Method (static factory) |
NumberFormat.getInstance() | Factory Method |
ResourceBundle.getBundle() | Factory Method |
DocumentBuilderFactory.newInstance() | Abstract Factory |
Additional resources:
- Design Patterns: Elements of Reusable Object-Oriented Software — Gang of Four (GoF)
Search Terms
design · patterns · java · creational · backend · architecture · full-stack · web · pattern · factory · singleton · builder · comparison · concepts · features · pitfalls · prototype · everyday · abstractfactory · calendar · copy · documentbuilderfactory · runtime · shallow