Design Patterns in C#: Complete Guide with Code Examples

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Design Patterns in C#: Complete Guide with Examples



Design patterns are proven solutions to typical problems in software development. In the C# and .NET ecosystem, they help create flexible, scalable, and maintainable architecture. In this article, we will break down four key patterns: Singleton, Factory Method, Observer, and Strategy, with real C# examples.



Why are design patterns needed in C#?

Patterns are not ready-made libraries or frameworks. They are conceptual templates that:

  • Simplify communication — developers use a unified terminology
  • Improve code quality — reduce coupling and increase flexibility
  • Save time — no need to reinvent the wheel
  • Adhere to SOLID principles — most patterns implement these principles


1. Singleton

Purpose: Ensures that a class has only one instance and provides a global point of access to it.



When to use:

  • Loggers
  • Configuration managers
  • Database connection pools
  • Caches


C# example (thread-safe implementation):

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

private readonly Dictionary<string, string> _settings;

private ConfigurationManager() { _settings = new Dictionary<string, string> { { "ConnectionString", "Server=localhost;Database=myDb" }, { "ApiKey", "12345-ABCDE" } }; }

public static ConfigurationManager Instance => _instance.Value;

public string GetSetting(string key) { return _settings.TryGetValue(key, out var value) ? value : null; }}

// Usagevar config = ConfigurationManager.Instance;Console.WriteLine(config.GetSetting("ConnectionString"));


Advantages:

  • Controlled access to the single instance
  • Lazy initialization (Lazy<T>)
  • Thread safety without locks


2. Factory Method

Purpose: Defines an interface for creating an object but lets subclasses alter the type of object that will be created.



When to use:

  • When the specific type of object is not known in advance
  • When object creation requires complex logic
  • To separate creation code from usage code


C# example:

// Abstract productpublic interface ILogger{    void Log(string message);}

// Concrete productspublic class FileLogger : ILogger{ public void Log(string message) => File.AppendAllText("log.txt", $"{DateTime.Now}: {message}");}

public class ConsoleLogger : ILogger{ public void Log(string message) => Console.WriteLine($"{DateTime.Now}: {message}");}

// Creator with factory methodpublic abstract class LoggerFactory{ public abstract ILogger CreateLogger(); public void LogMessage(string message) { var logger = CreateLogger(); logger.Log(message); }}

public class FileLoggerFactory : LoggerFactory{ public override ILogger CreateLogger() => new FileLogger();}

public class ConsoleLoggerFactory : LoggerFactory{ public override ILogger CreateLogger() => new ConsoleLogger();}

// UsageLoggerFactory factory = new ConsoleLoggerFactory();factory.LogMessage("Hello, world!");


3. O

Blogs

Book Recommendations