Showing posts with label Design Patterns. Show all posts
Showing posts with label Design Patterns. Show all posts

Saturday, June 27, 2026

Prototype Design Pattern


Singleton Design Pattern

Part 2.1 – Singleton Design Pattern

Series: Design Patterns in C# and ASP.NET Core

Part: 2.1 – Creational Design Patterns – Singleton Pattern

Target Audience: Beginners to Advanced .NET Developers


Table of Contents

  1. Introduction

  2. What is the Singleton Pattern?

  3. Why Do We Need the Singleton Pattern?

  4. Problem Statement

  5. Real-World Analogy

  6. Key Characteristics

  7. UML Class Diagram

  8. Basic Singleton Implementation

  9. Thread-Safe Singleton

  10. Lazy Singleton

  11. Singleton in ASP.NET Core

  12. Real-World Use Cases

  13. Advantages

  14. Disadvantages

  15. Best Practices

  16. Common Mistakes

  17. Singleton vs Static Class

  18. Interview Questions

  19. Summary


Introduction

As applications become more complex, there are situations where creating multiple instances of a class is unnecessary or even harmful. Consider services such as logging, application configuration, caching, or database connection management. If every component creates its own instance, the application may waste memory, produce inconsistent state, or become difficult to manage.

The Singleton Design Pattern solves this problem by ensuring that only one instance of a class exists throughout the application's lifetime while providing a single, globally accessible point to that instance.

The Singleton Pattern is one of the Creational Design Patterns introduced by the Gang of Four (GoF) and is widely used in enterprise applications, including those built with C# and ASP.NET Core.


What is the Singleton Pattern?

Definition

The Singleton Pattern ensures that:

  • Only one instance of a class can exist.

  • A global access point is provided to that instance.

  • The instance is created in a controlled manner.

In other words, regardless of how many times your application requests the object, it always receives the same instance.


Why Do We Need the Singleton Pattern?

Imagine a logging service used throughout an application.

Without Singleton:

  • Every class creates its own logger.

  • Multiple log files or streams may be opened.

  • Memory usage increases.

  • Configuration may become inconsistent.

With Singleton:

  • One shared logger handles all logging requests.

  • Consistent behavior is maintained.

  • Resource usage is minimized.


Problem Statement

Suppose you have a configuration manager.

public class ConfigurationManager
{
}

Now imagine different parts of the application create their own instances:

var config1 = new ConfigurationManager();
var config2 = new ConfigurationManager();
var config3 = new ConfigurationManager();

This creates three independent objects, which may each load the same configuration and consume additional resources unnecessarily.

The Singleton Pattern ensures there is only one shared instance.


Real-World Analogy

Imagine an office with one printer.

Hundreds of employees can send print jobs, but they all use the same physical printer.

The printer represents the Singleton instance:

  • One printer

  • Many users

  • Shared access

Another example is the President of a company. There is only one CEO at a time, and everyone interacts with that same person rather than creating new CEOs.


Key Characteristics

  • Only one object exists.

  • Constructor is private.

  • Object creation is controlled internally.

  • Global access point is provided.

  • Prevents accidental instantiation.


UML Class Diagram

                +----------------------+
                |     Singleton        |
                +----------------------+
                | - instance           |
                +----------------------+
                | - Singleton()        |
                | + Instance           |
                | + DoWork()           |
                +----------------------+

Explanation:

  • The constructor is private.

  • The static Instance property returns the single object.

  • Clients access the object through Instance.


Basic Singleton Implementation

public sealed class Logger
{
    private static Logger _instance;

    private Logger()
    {
    }

    public static Logger Instance
    {
        get
        {
            if (_instance == null)
            {
                _instance = new Logger();
            }

            return _instance;
        }
    }

    public void Log(string message)
    {
        Console.WriteLine($"[LOG] {message}");
    }
}

Usage

class Program
{
    static void Main()
    {
        Logger logger1 = Logger.Instance;
        Logger logger2 = Logger.Instance;

        logger1.Log("Application Started");

        Console.WriteLine(object.ReferenceEquals(logger1, logger2));
    }
}

Output

[LOG] Application Started
True

True confirms that both variables reference the same object.


Why Use sealed?

The sealed keyword prevents other classes from inheriting the Singleton class.

public sealed class Logger
{
}

This avoids scenarios where inheritance could accidentally allow multiple instances or alter the Singleton's intended behavior.


Thread-Safe Singleton

The basic implementation is not thread-safe. If two threads access Instance simultaneously, they could each create a new object.

A common solution is to use a lock.

public sealed class Logger
{
    private static Logger _instance;
    private static readonly object _lock = new object();

    private Logger()
    {
    }

    public static Logger Instance
    {
        get
        {
            lock (_lock)
            {
                if (_instance == null)
                {
                    _instance = new Logger();
                }

                return _instance;
            }
        }
    }
}

This guarantees that only one thread creates the instance.


Lazy Singleton

The .NET Framework provides the Lazy<T> class, which simplifies thread-safe lazy initialization.

public sealed class Logger
{
    private static readonly Lazy<Logger> _instance =
        new Lazy<Logger>(() => new Logger());

    private Logger()
    {
    }

    public static Logger Instance => _instance.Value;

    public void Log(string message)
    {
        Console.WriteLine($"[LOG] {message}");
    }
}

Why prefer Lazy<T>?

  • Thread-safe by default

  • Cleaner implementation

  • Object created only when first accessed

  • Recommended for most new applications


Singleton in ASP.NET Core

ASP.NET Core has built-in support for singleton services through Dependency Injection.

Service

public interface ILoggerService
{
    void Log(string message);
}

public class LoggerService : ILoggerService
{
    public void Log(string message)
    {
        Console.WriteLine(message);
    }
}

Register in Program.cs

builder.Services.AddSingleton<ILoggerService, LoggerService>();

Inject into a Controller

public class HomeController : Controller
{
    private readonly ILoggerService _logger;

    public HomeController(ILoggerService logger)
    {
        _logger = logger;
    }

    public IActionResult Index()
    {
        _logger.Log("Home page requested.");
        return View();
    }
}

The Dependency Injection container creates one instance of LoggerService and shares it across the application's lifetime.

Note: In ASP.NET Core, prefer using the built-in Dependency Injection container (AddSingleton) instead of implementing your own Singleton class unless you have a specific reason.


Real-World Use Cases

The Singleton Pattern is suitable for services that represent shared application-wide resources.

Use CaseWhy Singleton Fits
Logging ServiceA single logging pipeline simplifies management.
Application ConfigurationOne shared configuration source avoids duplication.
In-Memory CacheCentralized cache improves consistency.
License ManagerOne authority validates application licensing.
Printer SpoolerCoordinates access to a shared printer.
Feature Flag ServiceMaintains a consistent set of feature toggles.

Advantages

  • Ensures a single shared instance.

  • Reduces unnecessary object creation.

  • Provides a centralized access point.

  • Conserves memory and resources.

  • Simplifies management of shared services.

  • Works well with Dependency Injection.


Disadvantages

  • Introduces global state, which can make reasoning about the application harder.

  • Can complicate unit testing if overused.

  • May hide dependencies if accessed directly instead of through Dependency Injection.

  • Incorrect implementations may not be thread-safe.

  • Overuse can lead to tightly coupled code.


Best Practices

  • Prefer Lazy<T> for manual Singleton implementations.

  • Use sealed to prevent inheritance.

  • Keep Singleton classes focused on one responsibility.

  • Avoid storing mutable global state unless necessary.

  • In ASP.NET Core, use AddSingleton through the DI container whenever possible.

  • Inject Singleton services into consumers rather than calling static Instance properties throughout the codebase.


Common Mistakes

Public Constructor

public Logger()
{
}

This allows anyone to create additional instances.

Correct approach: Make the constructor private.


Ignoring Thread Safety

In multi-threaded applications, a non-thread-safe Singleton can produce multiple instances.

Correct approach: Use Lazy<T> or proper synchronization.


Using Singleton Everywhere

Not every service should be a Singleton.

For example, classes that maintain request-specific or user-specific state are usually better suited to scoped or transient lifetimes in ASP.NET Core.


Singleton vs Static Class

FeatureSingletonStatic Class
InstanceOne objectNo object
Implements Interfaces✔ Yes✖ No
Dependency Injection✔ Yes✖ No
Inheritance✔ Can implement interfaces (though the class itself is often sealed)✖ Not supported
Can Hold State✔ Yes✔ Yes (static state)
Lazy Initialization✔ YesLimited (type initialization semantics)
Supports Polymorphism✔ Yes✖ No

When to choose which?

  • Use a Singleton when you need an object with identity that participates in Dependency Injection and can implement interfaces.

  • Use a static class for stateless utility methods, such as mathematical helpers or string manipulation functions.


Frequently Asked Interview Questions

1. What is the Singleton Design Pattern?

It is a creational design pattern that ensures only one instance of a class exists and provides a global access point to that instance.


2. Why is the constructor private?

A private constructor prevents external code from creating additional instances using the new keyword.


3. Why should a Singleton class often be sealed?

To prevent inheritance from changing or bypassing the Singleton behavior.


4. Is the basic Singleton implementation thread-safe?

No. Concurrent access can result in multiple instances being created.


5. What is the recommended Singleton implementation in modern C#?

Using Lazy<T> for manual implementations, or leveraging the ASP.NET Core Dependency Injection container with AddSingleton.


6. Can Singleton work with Dependency Injection?

Yes. In fact, ASP.NET Core encourages registering shared services with AddSingleton instead of implementing the pattern manually in many cases.


7. What are some real-world examples of Singleton?

  • Logging services

  • Configuration providers

  • In-memory caches

  • Printer spoolers

  • Feature flag managers


Summary

The Singleton Design Pattern is one of the simplest yet most widely used creational patterns. It ensures that only one instance of a class exists and provides controlled global access to that instance. While manual implementations are useful for understanding the concept, modern ASP.NET Core applications typically rely on the built-in Dependency Injection container to manage singleton lifetimes.

Used appropriately, Singleton can improve resource management and maintain consistent application-wide behavior. However, it should be applied thoughtfully, avoiding unnecessary global state and ensuring thread safety where required.


What's Next?

In Part 2.2, we'll explore the Factory Method Design Pattern, where you'll learn:

  • Why direct object creation with new can become a maintenance problem

  • How the Factory Method Pattern decouples object creation from object usage

  • UML Class Diagram

  • Complete C# examples

  • ASP.NET Core implementation

  • Real-world scenarios

  • Advantages and disadvantages

  • Factory Method vs. Simple Factory

  • Common interview questions

This will build on the Singleton concepts and further strengthen your understanding of creational design patterns in modern .NET development.

Factory Design Pattern Part 2.2

Introduction to Design Patterns


Thursday, September 18, 2025

Design Patterns Explained: GoF & Modern Patterns with Real-World Examples

In software development, Design Patterns act as proven solutions to common problems. Instead of reinventing the wheel, developers can apply these reusable patterns to build scalable, maintainable, and robust applications.

This article explains Gang of Four (GoF) design patterns and also highlights modern design patterns widely used in cloud, microservices, and enterprise systems.


📌 What Are Design Patterns?

Design patterns are general, reusable solutions to recurring software problems. They are not code snippets but guidelines on how to solve design challenges effectively.

Benefits of using design patterns:

  • Improve code reusability & readability

  • Promote best practices & standardization

  • Reduce development & maintenance time

  • Help in team communication (common vocabulary)


🏛 Gang of Four (GoF) Design Patterns

The GoF introduced 23 design patterns in their famous book "Design Patterns: Elements of Reusable Object-Oriented Software".

They are categorized into three groups:

1. Creational Patterns – Object creation

Help in creating objects without exposing the creation logic.

  • Singleton → Ensures only one instance exists.

    public sealed class Logger
    {
        private static readonly Logger _instance = new Logger();
        private Logger() { }
        public static Logger Instance => _instance;
    }
    

    ✅ Used in logging, caching, config management.

  • Factory Method → Subclasses decide object creation.

  • Abstract Factory → Create families of related objects.

  • Builder → Step-by-step object construction.

  • Prototype → Clone objects without depending on their class.


2. Structural Patterns – Class & object composition

Define how objects are composed for flexibility.

  • Adapter → Converts one interface into another.
    ✅ Example: Wrapping an old payment gateway API to fit into a new e-commerce app.

  • Decorator → Add responsibilities dynamically.

  • Facade → Provide a simple interface to a complex system.

  • Composite → Treat individual & group objects uniformly (tree structure).

  • Proxy → Control access (like lazy loading, security).


3. Behavioral Patterns – Object communication

Define how objects interact & responsibilities are distributed.

  • Observer → One-to-many dependency.

    public interface IObserver { void Update(string msg); }
    public class User : IObserver
    {
        private string _name;
        public User(string name) => _name = name;
        public void Update(string msg) => 
    Console.WriteLine($"{_name} got update: {msg}");
    }
    public class NotificationService
    {
        private List<IObserver> observers = new();
        public void Subscribe(IObserver o) => observers.Add(o);
        public void Notify(string msg)
        {
            foreach (var o in observers) o.Update(msg);
        }
    }
    

    ✅ Used in push notifications, event-driven systems.

  • Strategy → Choose algorithm at runtime.

  • Command → Encapsulate actions as objects.

  • State → Change behavior based on object state.

  • Mediator, Memento, Interpreter, Visitor, Template Method, Chain of Responsibility, Iterator.


🚀 Modern / New Design Patterns (Beyond GoF)

Today’s software systems (Cloud, Microservices, Big Data, AI) need patterns beyond GoF. Here are some important ones:

1. Dependency Injection (DI)

  • Promotes loose coupling by injecting dependencies instead of creating them inside a class.

  • Widely used in .NET Core, Spring Boot, Angular.

    public class OrderService
    {
        private readonly IPaymentGateway _paymentGateway;
        public OrderService(IPaymentGateway paymentGateway)
     => _paymentGateway = paymentGateway;
        public void PlaceOrder() => _paymentGateway.Process();
    }
    

2. Repository Pattern

  • Abstracts database access to keep business logic independent.

  • Used in DDD (Domain-Driven Design) and enterprise apps.


3. CQRS (Command Query Responsibility Segregation)

  • Separates read and write operations for better scalability.

  • Common in microservices + event sourcing.


4. Saga Pattern (Microservices)


5. Event Sourcing

  • State changes are stored as events (not just final values).

  • Used in banking, e-commerce order history, blockchain-like systems.


6. Circuit Breaker Pattern

  • Prevents cascading failures by stopping requests to a failing service.

  • Implemented in Polly (.NET), Resilience4j (Java).


7. API Gateway Pattern

  • Acts as a single entry point for microservices.

  • Handles routing, load balancing, authentication, caching.


8. Strangler Fig Pattern

  • Gradually replaces legacy systems by routing new features to modern services while
     old ones still run.


🔑 Conclusion

  • GoF Patterns are still foundational for object-oriented design.

  • Modern patterns are essential for cloud, distributed systems, and microservices.

  • By combining both, developers can build scalable, reliable, and future-proof applications.


Don't Copy

Protected by Copyscape Online Plagiarism Checker