डिज़ाइन पैटर्न
What is it and why? (a real-life analogy)
Imagine you are building a house. If you start construction from scratch every time — digging a pit, mixing concrete, sawing boards — one house would take years. But if you have proven blueprints (standard house designs), you simply take a ready-made solution: "here will be a load-bearing wall, here a window, and the roof will be gable." You are not reinventing the wheel; you are using the experience of thousands of builders before you.
The same is true in programming. Design patterns (or simply "patterns") are ready-made, time-tested solutions to typical problems. They are not ready-made code that can be copied, but rather a template of thought, a way of organizing classes and objects so that the code is:
- Understandable (other developers will immediately guess what you wanted to do)
- Flexible (easy to add new features without breaking old ones)
- Reusable (write once — use in a hundred projects)
Important: Patterns are not magic and not a mandatory requirement. They are a tool. Like a hammer: if you are driving nails, it is indispensable, but if you want to screw in a screw, it is better to take a screwdriver. Use patterns only when they actually solve a problem.
Real-life analogy: Imagine you are a chef. You have a set of basic recipes: "how to boil broth", "how to make béchamel sauce", "how to knead dough." Every time an order comes in, you don't invent a new way to boil broth — you take a ready-made recipe and adapt it slightly for the specific dish. Patterns are the same kind of "recipes" for a programmer.
There are 23 classic patterns in total, described in the "Gang of Four" book. They are divided into three groups:
- Creational — how to conveniently create objects (for example, not through
new, but through a special method) - Structural — how to assemble classes into larger structures (like building a wall from bricks)
- Behavioral — how objects communicate with each other (like people in an office passing documents to each other)
Theory: how it works from the inside
Let's look "under the hood" of the most popular pattern — Singleton. It is needed when your program must have exactly one instance of a certain class. For example, a database connection, a logger (error recording), or application settings.
How it works from the inside:
- The class constructor is made private. This means no one from outside can write
new MyClass(). - A static field is created that stores the single instance of the class.
- A static method is created (usually called
GetInstance()or a propertyInstance), which checks: if the instance already exists — return it; if not — create a new one and return it.
Here is what it looks like in a diagram:
┌─────────────────────────┐
│ Singleton │
├─────────────────────────┤
│ - instance: Singleton │ ← static field (single instance)
├─────────────────────────┤
│ - Singleton() │ ← private constructor
│ + GetInstance(): Singleton│ ← static access method
└─────────────────────────┘
When you call Singleton.GetInstance() for the first time, an object is created. The second, third, hundredth time — the same object is returned. It's like having one key to a safe: you can make as many copies of the key (method calls) as you want, but the safe (object) remains one.
Important: In multithreaded programs (when several parts of the code work simultaneously), a regular Singleton can break — two threads can simultaneously check that the instance does not exist and create two objects. For this, a "lock" is used — we will look at this in the examples.
Syntax in C# (.NET): all forms and variants
In C#, there are several ways to implement patterns. Let's break down the main forms using the same Singleton as an example, and then move on to other patterns.
1. Simple Singleton (not thread-safe)
public class SimpleSingleton
{
// Static field for storing the single instance
private static SimpleSingleton _instance;
// Private constructor — no one can create an object via new
private SimpleSingleton()
{
Console.WriteLine("A new instance of SimpleSingleton has been created");
}
// Static property for accessing the instance
public static SimpleSingleton Instance
{
get
{
// If the instance has not been created yet — create it
if (_instance == null)
{
_instance = new SimpleSingleton();
}
// Return the existing instance
return _instance;
}
}
public void DoSomething()
{
Console.WriteLine("The single instance is working");
}
}
2. Thread-safe Singleton with locking (double-check locking)
public class ThreadSafeSingleton
{
// Object for locking (lock)
private static readonly object _lock = new object();
private static ThreadSafeSingleton _instance;
private ThreadSafeSingleton() { }
public static ThreadSafeSingleton Instance
{
get
{
// First check — without locking (fast)
if (_instance == null)
{
// Lock access for other threads
lock (_lock)
{
// Second check — already inside the lock