Software design patterns are often described as the timeless building blocks of software engineering. They originated from the famous “Gang of Four” (GoF) book published in 1994, which categorized patterns into creational, structural, and behavioral families. Among them, Singleton and Factory patterns have been two of the most widely used—and hotly debated—over the years.
But in today’s world of microservices, dependency injection, and functional programming, do these patterns still hold relevance? Let’s revisit them with modern eyes.
The Singleton Pattern: Friend or Foe?
The Singleton pattern ensures a class has only one instance and provides a global access point to it. Think of it like having a single manager in an office who coordinates resources.
Classic Implementation (Java Example)
public class Singleton {
private static Singleton instance;
private Singleton() { }
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
This ensures only one object is created and reused throughout the application.
Why Developers Loved It
- Global state management: Easy access to shared resources like loggers, caches, or configuration.
- Memory efficiency: Prevents redundant instantiations.
Why Developers Hate It
- Hidden dependencies: Any class accessing the singleton is tightly coupled to it.
- Testing nightmare: Mocking singletons is cumbersome.
- Global state is risky: Makes applications less predictable in multi-threaded environments.
The Factory Pattern: Shaping Object Creation
The Factory Method or Factory Class provides a way to create objects without specifying the exact class of the object that will be created. It’s about delegating the “new” keyword to a dedicated creator.
Example (Python)
class Shape:
def draw(self):
pass
class Circle(Shape):
def draw(self):
print("Drawing a Circle")
class Square(Shape):
def draw(self):
print("Drawing a Square")
class ShapeFactory:
@staticmethod
def get_shape(shape_type):
if shape_type == "circle":
return Circle()
elif shape_type == "square":
return Square()
return None
shape = ShapeFactory.get_shape("circle")
shape.draw()
Here, the client code doesn’t need to know how a Circle or Square is instantiated—just asks the factory.
Relevance in Today’s World
| Pattern | Then (1990s–2000s) | Now (Cloud, Microservices, DI) |
|---|---|---|
| Singleton | Simplified global access to resources. | Often replaced by dependency injection containers, environment configs, or service registries. Used carefully in logging or caching scenarios. |
| Factory | Decoupled object creation logic. | Still highly relevant. Factories form the backbone of frameworks (Spring, Angular, Django) that manage object lifecycles dynamically. |
Visualizing the Difference
Here’s a simplified diagram to compare the patterns:
Singleton: ┌────────────┐ │ Client │ └─────┬──────┘ │ ┌─────▼──────┐ │ Singleton │───> One global instance └────────────┘ Factory: ┌────────────┐ │ Client │ └─────┬──────┘ │ ┌─────▼──────┐ │ Factory │───> Creates many different objects └────────────┘
The Singleton centralizes state, while the Factory centralizes object creation.
A Real-World Example: Logging System
To really see the differences, let’s imagine building a logging system. Applications need logs everywhere, and they are a great use case to compare Singleton and Factory.
Singleton Approach
With Singleton, the logging system ensures only one global logger instance exists.
// Logger using Singleton
public class Logger {
private static Logger instance;
private Logger() { }
public static synchronized Logger getInstance() {
if (instance == null) {
instance = new Logger();
}
return instance;
}
public void log(String message) {
System.out.println("[LOG] " + message);
}
}
// Client
public class App {
public static void main(String[] args) {
Logger logger = Logger.getInstance();
logger.log("Application started");
}
}
Pros
- Always the same logger across the entire application.
- Easy to use, no setup required.
Cons
- Harder to replace with a mock logger in tests.
- Not flexible if we want different loggers (e.g., file logger vs. console logger).
Factory Approach
Now, let’s use a Factory. Instead of a single logger, we can ask a factory to give us the type of logger we need.
// Logger interface
public interface Logger {
void log(String message);
}
// Console logger
public class ConsoleLogger implements Logger {
public void log(String message) {
System.out.println("[Console] " + message);
}
}
// File logger
public class FileLogger implements Logger {
public void log(String message) {
// imagine writing to a file
System.out.println("[File] " + message);
}
}
// Logger Factory
public class LoggerFactory {
public static Logger getLogger(String type) {
switch (type.toLowerCase()) {
case "file":
return new FileLogger();
case "console":
default:
return new ConsoleLogger();
}
}
}
// Client
public class App {
public static void main(String[] args) {
Logger consoleLogger = LoggerFactory.getLogger("console");
consoleLogger.log("Application started on console");
Logger fileLogger = LoggerFactory.getLogger("file");
fileLogger.log("Application started in file");
}
}
Pros
- Very flexible: choose different loggers depending on environment or configuration.
- Easier to extend (add
DatabaseLogger,CloudLogger, etc. without changing client code). - More testable (inject a mock logger if needed).
Cons
- More setup and boilerplate.
- Not always necessary if you only ever need one logger.
Side-by-Side Comparison
| Aspect | Singleton Logger | Factory Logger |
|---|---|---|
| Instances | One global instance | Multiple instances depending on need |
| Flexibility | Rigid: only one logger type | Flexible: can support many loggers |
| Testing | Hard to mock | Easy to inject mocks |
| Use Case | Global configuration, cache, shared state | Pluggable systems, extensibility |
Which Should You Use?
- If your app truly requires one global logger (e.g., centralized configuration in a small app), a Singleton is fine.
- If you’re building a scalable system with different logging backends (console, file, cloud), a Factory is more future-proof.
👉 This practical example shows how Singleton trades flexibility for simplicity, while Factory trades simplicity for flexibility.
Modern Best Practices
- Singletons: Use sparingly. In modern applications, dependency injection frameworks (e.g., Spring in Java, Dagger/Hilt in Android, or NestJS in Node.js) provide controlled, testable ways to manage shared resources.
- Factories: Still powerful! They are especially valuable in situations like:
- Plug-in architectures (e.g., choosing different payment providers).
- Creating objects with complex setup logic.
- Framework-level abstractions where the client shouldn’t care about object internals.
Final Thoughts
Are Singleton and Factory still relevant?
- Singleton: less relevant as a design pattern because frameworks now handle instance management better. But conceptually, a single shared instance still makes sense in logging, caching, and configurations.
- Factory: very much alive and thriving, serving as the backbone of modern frameworks and architectures.
So instead of asking if these patterns are still useful, a better question might be: In what context should I apply them today?
Further Reading
- Gang of Four Design Patterns
- Singleton Pattern in Modern Development
- Factory Method Pattern Explained
- Martin Fowler on Dependency Injection
Thank you!
We will contact you soon.
Eleftheria DrosopoulouSeptember 22nd, 2025Last Updated: September 17th, 2025

This site uses Akismet to reduce spam. Learn how your comment data is processed.