Design patterns are typical solutions to common problems in software design. They are like blueprints that you can customize to solve a particular design problem in your code. Understanding design patterns is crucial for writing clean, maintainable, and scalable code.

Key Concepts

  1. What are Design Patterns?

    • Reusable solutions to common problems in software design.
    • Not code, but templates for how to solve a problem in various situations.
    • Help in improving code readability and reusability.
  2. Types of Design Patterns:

    • Creational Patterns: Deal with object creation mechanisms.
    • Structural Patterns: Deal with object composition or structure.
    • Behavioral Patterns: Deal with object interaction and responsibility.

Creational Patterns

Singleton Pattern

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

Example:

public class Singleton
{
    private static Singleton instance = null;
    private static readonly object padlock = new object();

    Singleton()
    {
    }

    public static Singleton Instance
    {
        get
        {
            lock (padlock)
            {
                if (instance == null)
                {
                    instance = new Singleton();
                }
                return instance;
            }
        }
    }
}

Explanation:

  • The Singleton class has a private static variable instance that holds the single instance of the class.
  • The constructor is private to prevent instantiation from outside the class.
  • The Instance property checks if the instance is null and creates it if necessary, ensuring thread safety with a lock.

Factory Method Pattern

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

Example:

public abstract class Product
{
    public abstract string GetName();
}

public class ConcreteProductA : Product
{
    public override string GetName() => "Product A";
}

public class ConcreteProductB : Product
{
    public override string GetName() => "Product B";
}

public abstract class Creator
{
    public abstract Product FactoryMethod();
}

public class ConcreteCreatorA : Creator
{
    public override Product FactoryMethod() => new ConcreteProductA();
}

public class ConcreteCreatorB : Creator
{
    public override Product FactoryMethod() => new ConcreteProductB();
}

Explanation:

  • Product is an abstract class with a method GetName.
  • ConcreteProductA and ConcreteProductB are concrete implementations of Product.
  • Creator is an abstract class with a method FactoryMethod.
  • ConcreteCreatorA and ConcreteCreatorB implement FactoryMethod to return instances of ConcreteProductA and ConcreteProductB, respectively.

Structural Patterns

Adapter Pattern

Allows incompatible interfaces to work together by converting the interface of a class into another interface that a client expects.

Example:

public interface ITarget
{
    void Request();
}

public class Adaptee
{
    public void SpecificRequest()
    {
        Console.WriteLine("Called SpecificRequest()");
    }
}

public class Adapter : ITarget
{
    private readonly Adaptee _adaptee;

    public Adapter(Adaptee adaptee)
    {
        _adaptee = adaptee;
    }

    public void Request()
    {
        _adaptee.SpecificRequest();
    }
}

Explanation:

  • ITarget defines the domain-specific interface.
  • Adaptee has an existing interface that needs adapting.
  • Adapter implements ITarget and translates the Request call to SpecificRequest.

Behavioral Patterns

Observer Pattern

Defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.

Example:

public interface IObserver
{
    void Update();
}

public class ConcreteObserver : IObserver
{
    public void Update()
    {
        Console.WriteLine("Observer has been updated.");
    }
}

public class Subject
{
    private List<IObserver> observers = new List<IObserver>();

    public void Attach(IObserver observer)
    {
        observers.Add(observer);
    }

    public void Detach(IObserver observer)
    {
        observers.Remove(observer);
    }

    public void Notify()
    {
        foreach (var observer in observers)
        {
            observer.Update();
        }
    }
}

Explanation:

  • IObserver defines an interface for objects that should be notified of changes.
  • ConcreteObserver implements IObserver and defines the Update method.
  • Subject maintains a list of observers and notifies them of any state changes.

Practical Exercise

Exercise: Implementing the Singleton Pattern

Task: Create a Singleton class that manages a configuration setting for an application.

Solution:

public class ConfigurationManager
{
    private static ConfigurationManager instance = null;
    private static readonly object padlock = new object();
    public string ConfigurationSetting { get; set; }

    ConfigurationManager()
    {
        ConfigurationSetting = "Default Setting";
    }

    public static ConfigurationManager Instance
    {
        get
        {
            lock (padlock)
            {
                if (instance == null)
                {
                    instance = new ConfigurationManager();
                }
                return instance;
            }
        }
    }
}

Explanation:

  • ConfigurationManager is a Singleton class with a private constructor.
  • It has a property ConfigurationSetting to store a configuration setting.
  • The Instance property ensures only one instance of ConfigurationManager is created.

Summary

In this section, we covered the basics of design patterns, including their importance and different types. We explored examples of creational, structural, and behavioral patterns, and provided a practical exercise to implement the Singleton pattern. Understanding and applying design patterns will help you write more efficient, maintainable, and scalable code.

© Copyright 2024. All rights reserved