Implementing 23 Common Design Patterns in C#

Implementing 23 Common Design Patterns in C#

These patterns are best practices for solving common object-oriented design problems.

Last updated 6/8/2023 9:51 PM
Token
48 min read
Category
.NET
Tags
.NET C#

Design patterns are generally divided into three main categories:

  • Creational Patterns
  • Structural Patterns
  • Behavioral Patterns

These patterns are best practices for solving common object-oriented design problems.

The following are 23 common design patterns with C# code examples:

Creational Patterns:

1. Singleton Pattern

public sealed class Singleton
{
    // Create a read-only static Singleton instance
    private static readonly Singleton instance = new Singleton();

    // Record the number of Singleton creations
    private static int instanceCounter = 0;

    // Public access point for the Singleton instance
    public static Singleton Instance
    {
        get
        {
            return instance;
        }
    }

    // Private constructor
    private Singleton()
    {
        instanceCounter++;
        Console.WriteLine("Instances Created " + instanceCounter);
    }

    // Add other Singleton class methods here
    public void LogMessage(string message)
    {
        Console.WriteLine("Message: " + message);
    }
}

In this example, we have a class named Singleton with a private constructor and a static read-only property Instance for accessing the unique instance of the Singleton class. We also have a LogMessage method to simulate some behavior of the Singleton class.

The following is a console application using this Singleton class:

class Program
{
    static void Main(string[] args)
    {
        Singleton fromEmployee = Singleton.Instance;
        fromEmployee.LogMessage("Message from Employee");

        Singleton fromBoss = Singleton.Instance;
        fromBoss.LogMessage("Message from Boss");
        Console.ReadLine();
    }
}

2. Factory Method Pattern

The Factory Method Pattern is a creational design pattern that provides an interface for creating objects but allows subclasses to decide which class to instantiate. Factory Method defers instantiation to subclasses.

Below is a simple example of the Factory Method Pattern implemented in C#:

// Abstract Product
public interface IProduct
{
    string Operation();
}

// Concrete Product A
public class ProductA : IProduct
{
    public string Operation()
    {
        return "{Result of ProductA}";
    }
}

// Concrete Product B
public class ProductB : IProduct
{
    public string Operation()
    {
        return "{Result of ProductB}";
    }
}

// Abstract Creator
public abstract class Creator
{
    public abstract IProduct FactoryMethod();
}

// Concrete Creator A
public class CreatorA : Creator
{
    public override IProduct FactoryMethod()
    {
        return new ProductA();
    }
}

// Concrete Creator B
public class CreatorB : Creator
{
    public override IProduct FactoryMethod()
    {
        return new ProductB();
    }
}

The code above defines two products, ProductA and ProductB, both implementing the IProduct interface. Then we have two Creator classes, CreatorA and CreatorB, both inheriting from the abstract base class Creator. CreatorA factory creates ProductA, and CreatorB factory creates ProductB.

The following is an example of using these factories and products:

class Program
{
    static void Main(string[] args)
    {
        // Create factory objects
        Creator creatorA = new CreatorA();
        Creator creatorB = new CreatorB();

        // Create product objects via factory method
        IProduct productA = creatorA.FactoryMethod();
        IProduct productB = creatorB.FactoryMethod();

        // Print results
        Console.WriteLine("ProductA says: " + productA.Operation());
        Console.WriteLine("ProductB says: " + productB.Operation());

        Console.ReadLine();
    }
}

When you run this program, it will display the results returned by the Operation methods of ProductA and ProductB. This shows we have successfully created product instances using the Factory Method pattern. Each factory class determines which product instance it creates. This approach allows client code to avoid directly instantiating product classes and only rely on the factory interface, increasing program flexibility.

3. Abstract Factory Pattern

The Abstract Factory Pattern is a creational design pattern that provides an interface for creating families of related or dependent objects without specifying their concrete classes. In this pattern, clients use classes through their abstract interfaces, allowing the pattern to replace implementation classes without affecting the client.

The following is a simple C# implementation of the Abstract Factory Pattern:

// Abstract Product: Animal
public interface IAnimal
{
    string Speak();
}

// Concrete Product: Dog
public class Dog : IAnimal
{
    public string Speak()
    {
        return "Bark Bark";
    }
}

// Concrete Product: Cat
public class Cat : IAnimal
{
    public string Speak()
    {
        return "Meow Meow";
    }
}

// Abstract Factory
public abstract class IAnimalFactory
{
    public abstract IAnimal CreateAnimal();
}

// Concrete Factory: Dog Factory
public class DogFactory : IAnimalFactory
{
    public override IAnimal CreateAnimal()
    {
        return new Dog();
    }
}

// Concrete Factory: Cat Factory
public class CatFactory : IAnimalFactory
{
    public override IAnimal CreateAnimal()
    {
        return new Cat();
    }
}

The code above defines two animals, Dog and Cat, both implementing the IAnimal interface. Then we have two factory classes, DogFactory and CatFactory, both inheriting from IAnimalFactory. DogFactory produces Dog, while CatFactory produces Cat.

The following is an example of using these factories and products:

class Program
{
    static void Main(string[] args)
    {
        // Create factories
        IAnimalFactory dogFactory = new DogFactory();
        IAnimalFactory catFactory = new CatFactory();

        // Use factories to create products
        IAnimal dog = dogFactory.CreateAnimal();
        IAnimal cat = catFactory.CreateAnimal();

        // Print results
        Console.WriteLine("Dog says: " + dog.Speak());
        Console.WriteLine("Cat says: " + cat.Speak());

        Console.ReadLine();
    }
}

When you run this program, it will print the results of the Speak methods of Dog and Cat, showing that we have successfully created product instances using the Abstract Factory pattern. This approach allows client code to avoid directly instantiating product classes and only rely on the factory interface, increasing program flexibility and extensibility.

4. Builder Pattern

The Builder Pattern is a creational design pattern that provides an interface for creating objects but allows the same building process to produce different products.

Below is a simple example of the Builder Pattern implemented in C#:

// Product
public class Car
{
    public string Engine { get; set; }
    public string Wheels { get; set; }
    public string Doors { get; set; }
}

// Abstract Builder
public abstract class CarBuilder
{
    protected Car car;

    public void CreateNewCar()
    {
        car = new Car();
    }

    public Car GetCar()
    {
        return car;
    }

    public abstract void SetEngine();
    public abstract void SetWheels();
    public abstract void SetDoors();
}

// Concrete Builder
public class FerrariBuilder : CarBuilder
{
    public override void SetEngine()
    {
        car.Engine = "V8";
    }

    public override void SetWheels()
    {
        car.Wheels = "18 inch";
    }

    public override void SetDoors()
    {
        car.Doors = "2";
    }
}

// Director
public class Director
{
    public Car Construct(CarBuilder carBuilder)
    {
        carBuilder.CreateNewCar();
        carBuilder.SetEngine();
        carBuilder.SetWheels();
        carBuilder.SetDoors();
        return carBuilder.GetCar();
    }
}

In the above code, Car is the product we want to create; CarBuilder is the abstract builder, defining the steps required to manufacture a product; FerrariBuilder is a concrete builder that implements all the steps defined by CarBuilder; Director is the director, telling the builder the order in which to execute the steps.

The following is an example of using this Builder pattern:

class Program
{
    static void Main(string[] args)
    {
        Director director = new Director();
        CarBuilder builder = new FerrariBuilder();
        Car ferrari = director.Construct(builder);

        Console.WriteLine($"Engine: {ferrari.Engine}, Wheels: {ferrari.Wheels}, Doors: {ferrari.Doors}");
        Console.ReadLine();
    }
}

When you run this program, you will see that we have successfully created a Car instance, with its parts created according to the way defined by FerrariBuilder. This demonstrates that we have successfully decoupled the construction process of a complex object using the Builder pattern, allowing the same construction process to create different representations.

5. Prototype Pattern

The Prototype Pattern is a creational design pattern that implements a prototype interface for creating a clone of the current object. This pattern is used when creating an object directly is costly, for example, when an object needs to be created after an expensive database operation.

Below is a simple example of the Prototype Pattern implemented in C#:

// Abstract Prototype
public interface IPrototype
{
    IPrototype Clone();
}

// Concrete Prototype
public class ConcretePrototype : IPrototype
{
    public string Name { get; set; }
    public int Value { get; set; }

    public IPrototype Clone()
    {
        // Implements deep copy
        return (ConcretePrototype)this.MemberwiseClone(); // Clones the concrete object.
    }
}

The code above defines a ConcretePrototype class that implements the IPrototype interface. The interface defines a Clone method for copying objects. In the ConcretePrototype class, we use the MemberwiseClone method to create a new cloned object.

The following is an example of using the Prototype pattern:

class Program
{
    static void Main(string[] args)
    {
        ConcretePrototype prototype = new ConcretePrototype();
        prototype.Name = "Original";
        prototype.Value = 10;

        Console.WriteLine("Original instance: " + prototype.Name + ", " + prototype.Value);

        ConcretePrototype clone = (ConcretePrototype)prototype.Clone();
        Console.WriteLine("Cloned instance: " + clone.Name + ", " + clone.Value);

        Console.ReadLine();
    }
}

In this example, we create a ConcretePrototype object, assign values to its properties, and then call the Clone method to create a new ConcretePrototype object. When we run this program, we see that the properties of the original object and the cloned object are the same, indicating that we have successfully cloned an object.

Execution flow:

  1. Create a concrete prototype object and assign values to its properties.
  2. Call the Clone method of the prototype object to create a new object with the same properties as the prototype.
  3. Print the properties of both the prototype and the cloned object to verify they are the same.

Structural Patterns:

1. Adapter Pattern

The Adapter Pattern's goal is to convert the interface of a class into another interface that clients expect. Adapter allows classes that would otherwise be incompatible due to interface mismatches to work together. Below is an example of the Adapter Pattern implemented in C#:

In this example, I will create an ITarget interface and an Adaptee class. Then I will create an adapter Adapter that implements the ITarget interface and uses the Adaptee class's methods to satisfy the ITarget requirements.

// Target interface, or the interface expected by the client.
public interface ITarget
{
    string GetRequest();
}

// The class that needs to be adapted.
public class Adaptee
{
    public string GetSpecificRequest()
    {
        return "Specific request.";
    }
}

// Adapter class, which satisfies the ITarget interface by internally encapsulating an Adaptee object.
public class Adapter : ITarget
{
    private readonly Adaptee _adaptee;

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

    public string GetRequest()
    {
        return $"This is '{this._adaptee.GetSpecificRequest()}'";
    }
}

// Client code, which is compatible with all objects that conform to the ITarget interface.
public class Client
{
    public void MakeRequest(ITarget target)
    {
        Console.WriteLine(target.GetRequest());
    }
}

Execution flow:

class Program
{
    static void Main(string[] args)
    {
        Adaptee adaptee = new Adaptee();
        ITarget target = new Adapter(adaptee);
        Client client = new Client();

        // Since the adapter makes Adaptee compatible with Client, we can call Client's MakeRequest method
        client.MakeRequest(target);

        // Wait for user input to prevent the console program from exiting immediately.
        Console.ReadKey();
    }
}

When running the above code, the following output will be printed on the console:

This is 'Specific request.'

In this example, Adapter is the class that adapts Adaptee to the ITarget interface. When the client (in this case, the Client class) calls the GetRequest method of the Adapter, the Adapter forwards the request to the GetSpecificRequest method of Adaptee.

In this instance, by using the Adapter class, we have enabled the originally incompatible Client and Adaptee classes to work together smoothly.

2. Bridge Pattern

The Bridge Pattern is a structural design pattern used to separate an abstraction from its implementation so that both can vary independently.

Below is a simple example of the Bridge Pattern implemented in C#:

// Implementor interface
public interface IImplementor
{
    void OperationImp();
}

// Concrete Implementor A
public class ConcreteImplementorA : IImplementor
{
    public void OperationImp()
    {
        Console.WriteLine("Concrete Implementor A");
    }
}

// Concrete Implementor B
public class ConcreteImplementorB : IImplementor
{
    public void OperationImp()
    {
        Console.WriteLine("Concrete Implementor B");
    }
}

// Abstraction
public abstract class Abstraction
{
    protected IImplementor implementor;

    public Abstraction(IImplementor implementor)
    {
        this.implementor = implementor;
    }

    public virtual void Operation()
    {
        implementor.OperationImp();
    }
}

// Refined Abstraction
public class RefinedAbstraction : Abstraction
{
    public RefinedAbstraction(IImplementor implementor) : base(implementor) { }

    public override void Operation()
    {
        Console.WriteLine("Refined Abstraction is calling implementor's method:");
        base.Operation();
    }
}

In this code, Abstraction is an abstract class that holds an instance of the IImplementor interface, through which it calls the implementor's methods. RefinedAbstraction is an extended abstraction that inherits from Abstraction. ConcreteImplementorA and ConcreteImplementorB are implementation classes that implement the IImplementor interface.

The following is an example of using this pattern:

class Program
{
    static void Main(string[] args)
    {
        IImplementor implementorA = new ConcreteImplementorA();
        Abstraction abstractionA = new RefinedAbstraction(implementorA);
        abstractionA.Operation();

        IImplementor implementorB = new ConcreteImplementorB();
        Abstraction abstractionB = new RefinedAbstraction(implementorB);
        abstractionB.Operation();

        Console.ReadLine();
    }
}

In this example, we create two instances of implementation classes, then two instances of abstraction classes, each containing an instance of an implementation class. When we call the Operation method of the abstraction class, it calls the OperationImp method of the implementation class.

Execution flow:

  1. Create instances of implementation classes.
  2. Create instances of abstraction classes; each abstraction instance holds an instance of an implementation class.
  3. Call the Operation method of the abstraction class, which calls the OperationImp method of the implementation class.

3. Composite Pattern

The Composite Pattern is a structural design pattern that allows you to compose objects into tree structures and treat individual objects and compositions uniformly. The main goal of this pattern is to make single objects and composite objects consistent.

Below is a simple example of the Composite Pattern implemented in C#:

// Abstract Component class
public abstract class Component
{
    protected string name;

    public Component(string name)
    {
        this.name = name;
    }

    public abstract void Add(Component c);
    public abstract void Remove(Component c);
    public abstract void Display(int depth);
}

// Leaf class
public class Leaf : Component
{
    public Leaf(string name) : base(name) { }

    public override void Add(Component c)
    {
        Console.WriteLine("Cannot add to a leaf");
    }

    public override void Remove(Component c)
    {
        Console.WriteLine("Cannot remove from a leaf");
    }

    public override void Display(int depth)
    {
        Console.WriteLine(new String('-', depth) + name);
    }
}

// Composite class
public class Composite : Component
{
    private List<Component> _children = new List<Component>();

    public Composite(string name) : base(name) { }

    public override void Add(Component component)
    {
        _children.Add(component);
    }

    public override void Remove(Component component)
    {
        _children.Remove(component);
    }

    public override void Display(int depth)
    {
        Console.WriteLine(new String('-', depth) + name);

        // Display each child node
        foreach (Component component in _children)
        {
            component.Display(depth + 2);
        }
    }
}

In this code, Component is an abstract component class with a name and defines add, remove, and display operations. Leaf is a leaf node that implements the Component operations. Composite is a component container that can add, remove, and display its child nodes.

The following is an example of using this pattern:

class Program
{
    static void Main(string[] args)
    {
        Composite root = new Composite("root");
        root.Add(new Leaf("Leaf A"));
        root.Add(new Leaf("Leaf B"));

        Composite comp = new Composite("Composite X");
        comp.Add(new Leaf("Leaf XA"));
        comp.Add(new Leaf("Leaf XB"));

        root.Add(comp);

        Composite comp2 = new Composite("Composite XY");
        comp2.Add(new Leaf("Leaf XYA"));
        comp2.Add(new Leaf("Leaf XYB"));

        comp.Add(comp2);

        root.Add(new Leaf("Leaf C"));

        // Add and remove in the composite
        Leaf leaf = new Leaf("Leaf D");
        root.Add(leaf);
        root.Remove(leaf);

        // Display the tree structure
        root.Display(1);

        Console.ReadLine();
    }
}

In this example, we create a root node and add two leaf nodes to it. Then we create a composite node and add two leaf nodes to it, and then add the composite node to the root. We also add another composite node inside the first composite. Finally, we add and remove a leaf node from the root, and then display the tree structure.

Execution flow:

  1. Create composite and leaf objects.
  2. Add leaf objects and other composite objects to composite objects via the Add method.
  3. Remove leaf objects from composite objects via the Remove method.
  4. Call the Display method of the composite object to display its structure.

4. Decorator Pattern

The Decorator Pattern is a structural design pattern that allows adding functionality to an object dynamically at runtime, providing a more flexible alternative to inheritance.

Below is a simple example of the Decorator Pattern implemented in C#:

// Abstract Component
public abstract class Component
{
    public abstract string Operation();
}

// Concrete Component
public class ConcreteComponent : Component
{
    public override string Operation()
    {
        return "ConcreteComponent";
    }
}

// Abstract Decorator
public abstract class Decorator : Component
{
    protected Component component;

    public Decorator(Component component)
    {
        this.component = component;
    }

    public override string Operation()
    {
        if (component != null)
        {
            return component.Operation();
        }
        else
        {
            return string.Empty;
        }
    }
}

// Concrete Decorator A
public class ConcreteDecoratorA : Decorator
{
    public ConcreteDecoratorA(Component comp) : base(comp) { }

    public override string Operation()
    {
        return $"ConcreteDecoratorA({base.Operation()})";
    }
}

// Concrete Decorator B
public class ConcreteDecoratorB : Decorator
{
    public ConcreteDecoratorB(Component comp) : base(comp) { }

    public override string Operation()
    {
        return $"ConcreteDecoratorB({base.Operation()})";
    }
}

In this code, Component is an abstract component defining an Operation method. ConcreteComponent is a concrete component that implements the Operation method of Component. Decorator is an abstract decorator that contains a Component object and overrides the Operation method. ConcreteDecoratorA and ConcreteDecoratorB are concrete decorators that inherit from Decorator and override the Operation method to add new functionality.

The following is an example of using this pattern:

class Program
{
    static void Main(string[] args)
    {
        // Basic component
        Component component = new ConcreteComponent();
        Console.WriteLine("Basic Component: " + component.Operation());

        // Decorated component
        Component decoratorA = new ConcreteDecoratorA(component);
        Console.WriteLine("A Decorated: " + decoratorA.Operation());

        Component decoratorB = new ConcreteDecoratorB(decoratorA);
        Console.WriteLine("B Decorated: " + decoratorB.Operation());

        Console.ReadLine();
    }
}

In this example, we first create a ConcreteComponent object and call its Operation method. Then we create a ConcreteDecoratorA object that decorates the ConcreteComponent and call its Operation method. Finally, we create a ConcreteDecoratorB object that decorates the ConcreteDecoratorA and call its Operation method. This way, we can dynamically add functionality at runtime.

Execution flow:

  1. Create a concrete component object and call its operation.
  2. Create a decorator object that decorates the concrete component and call its operation. In the operation, the decorator first calls the concrete component's operation, then performs additional operations.
  3. Create another decorator object that decorates the previous decorator and call its operation. In the operation, this decorator first calls the previous decorator's operation, then performs additional operations.

5. Facade Pattern

The Facade Pattern is a structural design pattern that provides a unified interface to a set of interfaces in a subsystem. Facade defines a higher-level interface that makes the subsystem easier to use.

Below is a simple example of the Facade Pattern implemented in C#:

// Subsystem A
public class SubSystemA
{
    public string OperationA()
    {
        return "SubSystemA, OperationA\n";
    }
}

// Subsystem B
public class SubSystemB
{
    public string OperationB()
    {
        return "SubSystemB, OperationB\n";
    }
}

// Subsystem C
public class SubSystemC
{
    public string OperationC()
    {
        return "SubSystemC, OperationC\n";
    }
}

// Facade class
public class Facade
{
    private SubSystemA a = new SubSystemA();
    private SubSystemB b = new SubSystemB();
    private SubSystemC c = new SubSystemC();

    public string OperationWrapper()
    {
        string result = "Facade initializes subsystems:\n";
        result += a.OperationA();
        result += b.OperationB();
        result += c.OperationC();
        return result;
    }
}

In this code, SubSystemA, SubSystemB, and SubSystemC are subsystems, each with an operation. Facade is a facade class that encapsulates operations on subsystems, providing a unified interface.

The following is an example of using this pattern:

class Program
{
    static void Main(string[] args)
    {
        Facade facade = new Facade();
        Console.WriteLine(facade.OperationWrapper());

        Console.ReadLine();
    }
}

In this example, we create a Facade object and call its OperationWrapper method. This method encapsulates operations on subsystems, allowing the client to operate the subsystems indirectly through the facade class rather than directly.

Execution flow:

  1. Create a facade object.

  2. Indirectly operate the subsystems by calling the facade object's method.

  3. Subsystem operations are encapsulated within the facade object's method; the client does not need to directly operate the subsystems.

6. Flyweight Pattern

The Flyweight Pattern is a structural design pattern primarily used to reduce the number of objects created, thereby reducing memory usage and improving performance. This type of design pattern belongs to the structural pattern category, providing a way to reduce the number of objects to improve the object structure required by an application.

Below is a simple example of the Flyweight Pattern implemented in C#:

// Flyweight class
public class Flyweight
{
    private string intrinsicState;

    // Constructor
    public Flyweight(string intrinsicState)
    {
        this.intrinsicState = intrinsicState;
    }

    // Business method
    public void Operation(string extrinsicState)
    {
        Console.WriteLine($"Intrinsic State = {intrinsicState}, Extrinsic State = {extrinsicState}");
    }
}

// Flyweight Factory class
public class FlyweightFactory
{
    private Dictionary<string, Flyweight> flyweights = new Dictionary<string, Flyweight>();

    public Flyweight GetFlyweight(string key)
    {
        if (!flyweights.ContainsKey(key))
        {
            flyweights[key] = new Flyweight(key);
        }

        return flyweights[key];
    }

    public int GetFlyweightCount()
    {
        return flyweights.Count;
    }
}

In this code, Flyweight is the flyweight class with an intrinsic state intrinsicState that is immutable. FlyweightFactory is the flyweight factory class that maintains a collection of flyweight objects.

The following is an example of using this pattern:

class Program
{
    static void Main(string[] args)
    {
        FlyweightFactory factory = new FlyweightFactory();

        Flyweight flyweightA = factory.GetFlyweight("A");
        flyweightA.Operation("A operation");

        Flyweight flyweightB = factory.GetFlyweight("B");
        flyweightB.Operation("B operation");

        Flyweight flyweightC = factory.GetFlyweight("A");
        flyweightC.Operation("C operation");

        Console.WriteLine($"Total Flyweights: {factory.GetFlyweightCount()}");

        Console.ReadLine();
    }
}

In this example, we create a FlyweightFactory object and use it to create two flyweight objects. Note that when we attempt to create a third flyweight object, the factory actually returns a reference to the first flyweight object because the intrinsic state of these two objects is the same.

Execution flow:

  1. Create a flyweight factory object.
  2. Obtain flyweight objects through the flyweight factory. If the object already exists, return the existing object; otherwise, create a new object.
  3. Perform the operation on the flyweight object.
  4. Display the current number of flyweight objects.

7. Proxy Pattern

The Proxy Pattern is a structural design pattern that provides a surrogate or placeholder for another object to control access to it. A proxy object can act as an intermediary between the client and the target object and add additional functionality.

Below is a simple example of the Proxy Pattern implemented in C#:

// Abstract Subject interface
public interface ISubject
{
    void Request();
}

// Real Subject
public class RealSubject : ISubject
{
    public void Request()
    {
        Console.WriteLine("RealSubject: Handling Request.");
    }
}

// Proxy
public class Proxy : ISubject
{
    private RealSubject _realSubject;

    public Proxy(RealSubject realSubject)
    {
        this._realSubject = realSubject;
    }

    public void Request()
    {
        if (this.CheckAccess())
        {
            this._realSubject.Request();
            this.LogAccess();
        }
    }

    public bool CheckAccess()
    {
        // Check if access is allowed
        Console.WriteLine("Proxy: Checking access prior to firing a real request.");
        return true;
    }

    public void LogAccess()
    {
        // Log the request
        Console.WriteLine("Proxy: Logging the time of request.");
    }
}

In this code, ISubject is an interface defining the Request method. RealSubject is a class that implements the ISubject interface. Proxy is a proxy class that also implements the ISubject interface and holds a reference to a RealSubject object.

The following is an example of using this pattern:

class Program
{
    static void Main(string[] args)
    {
        Console.WriteLine("Client: Executing the client code with a real subject:");
        RealSubject realSubject = new RealSubject();
        realSubject.Request();

        Console.WriteLine();

        Console.WriteLine("Client: Executing the same client code with a proxy:");
        Proxy proxy = new Proxy(realSubject);
        proxy.Request();

        Console.ReadLine();
    }
}

In this example, we first directly call the Request method of RealSubject, then call the same method through the proxy. Note that when calling the Request method through the proxy, the proxy also performs other operations, such as checking access and logging.

Execution flow:

  1. Create a real subject object and directly call its Request method.
  2. Create a proxy object that contains a reference to the real subject.
  3. Call the Request method through the proxy object. In this method, the proxy first checks access, then calls the real subject's Request method, and finally logs the request.

Behavioral Patterns:

1. Chain of Responsibility Pattern

The Chain of Responsibility Pattern is a behavioral design pattern that creates a chain of receiver objects for a request. This pattern gives more freedom to the request sender and allows the request to travel along the chain until an object handles it.

In this example, I will create an abstract Handler class and two concrete handlers ConcreteHandler1 and ConcreteHandler2. Each handler checks whether it can handle the request; if so, it handles it. If not, it passes the request to the next handler in the chain.

public abstract class Handler
{
    protected Handler successor;

    public void SetSuccessor(Handler successor)
    {
        this.successor = successor;
    }

    public abstract void HandleRequest(int request);
}

public class ConcreteHandler1 : Handler
{
    public override void HandleRequest(int request)
    {
        if (request >= 0 && request < 10)
        {
            Console.WriteLine($"{this.GetType().Name} handled request {request}");
        }
        else if (successor != null)
        {
            successor.HandleRequest(request);
        }
    }
}

public class ConcreteHandler2 : Handler
{
    public override void HandleRequest(int request)
    {
        if (request >= 10 && request < 20)
        {
            Console.WriteLine($"{this.GetType().Name} handled request {request}");
        }
        else if (successor != null)
        {
            successor.HandleRequest(request);
        }
    }
}

Execution flow:

class Program
{
    static void Main(string[] args)
    {
        // Set up the chain of responsibility
        Handler h1 = new ConcreteHandler1();
        Handler h2 = new ConcreteHandler2();

        h1.SetSuccessor(h2);

        // Generate and process requests
        int[] requests = { 2, 5, 14, 22, 18, 3, 27, 20 };

        foreach (int request in requests)
        {
            h1.HandleRequest(request);
        }

        Console.ReadKey();
    }
}

When running the above code, the following output will be printed on the console:

ConcreteHandler1 handled request 2
ConcreteHandler1 handled request 5
ConcreteHandler2 handled request 14
ConcreteHandler2 handled request 18
ConcreteHandler1 handled request 3

In this example, ConcreteHandler1 and ConcreteHandler2 are handlers that decide whether to process the incoming request. If ConcreteHandler1 cannot handle the request, it passes it to ConcreteHandler2. If ConcreteHandler2 also cannot handle it, the request is ignored.

This example demonstrates the core idea of the Chain of Responsibility pattern: creating a chain of objects to handle a request. In this chain, each handler decides whether to handle the request. If it cannot, it passes the request to the next handler in the chain.

2. Command Pattern

The Command Pattern is a data-driven design pattern that belongs to the behavioral pattern category. In the Command pattern, a request is encapsulated as an operation or action within an object. These requests are sent to an invoker object, which looks for an appropriate object that can handle the command and sends the command directly to that object, which then executes the command.

Below is a simple example of the Command Pattern implemented in C#:

// Command interface
public interface ICommand
{
    void Execute();
}

// Concrete Command class
public class ConcreteCommand : ICommand
{
    private Receiver receiver;

    public ConcreteCommand(Receiver receiver)
    {
        this.receiver = receiver;
    }

    public void Execute()
    {
        receiver.Action();
    }
}

// Receiver class
public class Receiver
{
    public void Action()
    {
        Console.WriteLine("Receiver performs an action");
    }
}

// Invoker or Sender class
public class Invoker
{
    private ICommand command;

    public void SetCommand(ICommand command)
    {
        this.command = command;
    }

    public void ExecuteCommand()
    {
        command.Execute();
    }
}

In this code, ICommand is the command interface defining the Execute method. ConcreteCommand is a concrete command class that implements the ICommand interface and holds a reference to a Receiver object. Invoker is the invoker or sender class that holds a reference to an ICommand object and can set the command via SetCommand and execute it via ExecuteCommand.

The following is an example of using this pattern:

class Program
{
    static void Main(string[] args)
    {
        Receiver receiver = new Receiver();
        ICommand command = new ConcreteCommand(receiver);
        Invoker invoker = new Invoker();

        invoker.SetCommand(command);
        invoker.ExecuteCommand();

        Console.ReadLine();
    }
}

In this example, we create a Receiver object, a ConcreteCommand object, and an Invoker object. Then we set the command via the SetCommand method of Invoker and execute the command via the ExecuteCommand method.

Execution flow:

  1. Create a receiver object.
  2. Create a concrete command object, passing the receiver object to it.
  3. Create an invoker or sender object.
  4. Set the command via the invoker object's SetCommand method.
  5. Execute the command via the invoker object's ExecuteCommand method.

3. Interpreter Pattern

The Interpreter Pattern is a behavioral design pattern used to solve problems with fixed grammar formats. It defines how to represent and parse grammar in a language.

Below is a simple example of the Interpreter Pattern implemented in C#:

// Abstract Expression
public interface IExpression
{
    bool Interpret(string context);
}

// Terminal Expression
public class TerminalExpression : IExpression
{
    private string data;

    public TerminalExpression(string data)
    {
        this.data = data;
    }

    public bool Interpret(string context)
    {
        if (context.Contains(data))
        {
            return true;
        }

        return false;
    }
}

// Non-terminal Expression
public class OrExpression : IExpression
{
    private IExpression expr1 = null;
    private IExpression expr2 = null;

    public OrExpression(IExpression expr1, IExpression expr2)
    {
        this.expr1 = expr1;
        this.expr2 = expr2;
    }

    public bool Interpret(string context)
    {
        return expr1.Interpret(context) || expr2.Interpret(context);
    }
}

In this code, IExpression is the abstract expression defining the Interpret method. TerminalExpression is a terminal expression that implements the IExpression interface. OrExpression is a non-terminal expression that also implements the IExpression interface.

The following is an example of using this pattern:

class Program
{
    static void Main(string[] args)
    {
        IExpression isMale = GetMaleExpression();
        IExpression isMarriedWoman = GetMarriedWomanExpression();

        Console.WriteLine($"John is male? {isMale.Interpret("John")}");
        Console.WriteLine($"Julie is a married women? {isMarriedWoman.Interpret("Married Julie")}");

        Console.ReadLine();
    }

    // Rule: Robert and John are male
    public static IExpression GetMaleExpression()
    {
        IExpression robert = new TerminalExpression("Robert");
        IExpression john = new TerminalExpression("John");
        return new OrExpression(robert, john);
    }

    // Rule: Julie is a married woman
    public static IExpression GetMarriedWomanExpression()
    {
        IExpression julie = new TerminalExpression("Julie");
        IExpression married = new TerminalExpression("Married");
        return new OrExpression(julie, married);
    }
}

In this example, we define two rules: "Robert and John are male" and "Julie is a married woman". We then create two expression objects representing these rules and use them to parse input.

Execution flow:

  1. Create terminal expression objects and non-terminal expression objects to represent rules.
  2. Call the Interpret method of the expression objects to parse the input string.
  3. Output the parsing result.

4. Iterator Pattern

The Iterator Pattern is a behavioral design pattern that provides a way to access the elements of an object without exposing its internal representation. Below is a simple example of the Iterator Pattern implemented in C#:

// Abstract Aggregate
public interface IAggregate
{
    IIterator CreateIterator();
    void Add(string item);
    int Count { get; }
    string this[int index] { get; set; }
}

// Concrete Aggregate
public class ConcreteAggregate : IAggregate
{
    private List<string> items = new List<string>();

    public IIterator CreateIterator()
    {
        return new ConcreteIterator(this);
    }

    public int Count
    {
        get { return items.Count; }
    }

    public string this[int index]
    {
        get { return items[index]; }
        set { items.Insert(index, value); }
    }

    public void Add(string item)
    {
        items.Add(item);
    }
}

// Abstract Iterator
public interface IIterator
{
    string First();
    string Next();
    bool IsDone { get; }
    string CurrentItem { get; }
}

// Concrete Iterator
public class ConcreteIterator : IIterator
{
    private ConcreteAggregate aggregate;
    private int current = 0;

    public ConcreteIterator(ConcreteAggregate aggregate)
    {
        this.aggregate = aggregate;
    }

    public string First()
    {
        return aggregate[0];
    }

    public string Next()
    {
        string ret = null;
        if (current < aggregate.Count - 1)
        {
            ret = aggregate[++current];
        }

        return ret;
    }

    public string CurrentItem
    {
        get { return aggregate[current]; }
    }

    public bool IsDone
    {
        get { return current >= aggregate.Count; }
    }
}

In this code, IAggregate is the abstract aggregate defining methods like CreateIterator, and ConcreteAggregate is the concrete aggregate implementing the IAggregate interface. IIterator is the abstract iterator defining methods like First and Next, and ConcreteIterator is the concrete iterator implementing the IIterator interface.

The following is an example of using this pattern:

class Program
{
    static void Main(string[] args)
    {
        IAggregate aggregate = new ConcreteAggregate();
        aggregate.Add("Item A");
        aggregate.Add("Item B");
        aggregate.Add("Item C");
        aggregate.Add("Item D");

        IIterator iterator = aggregate.CreateIterator();

        Console.WriteLine("Iterating over collection:");

        string item = iterator.First();
        while (item != null)
        {
            Console.WriteLine(item);
            item = iterator.Next();
        }

        Console.ReadLine();
    }
}

In this example, we create a ConcreteAggregate object and add several elements. Then we create an iterator via the CreateIterator method and use it to traverse all elements in the collection.

Execution flow:

  1. Create an aggregate object and add some elements.
  2. Create an iterator via the aggregate object's CreateIterator method.
  3. Get the first element via the iterator's First method, then get subsequent elements via the Next method until no more elements are available.

5. Mediator Pattern

The Mediator Pattern is a behavioral design pattern that allows you to reduce complex communication between a group of objects. It provides a mediator object that handles communication between objects in the group instead of them communicating directly.

First, let's define a mediator interface and a concrete mediator:

// Mediator interface declares methods for interacting with components.
public interface IMediator
{
    void Notify(object sender, string ev);
}

// Concrete Mediators implement collaborative behavior and coordinate multiple components.
public class ConcreteMediator : IMediator
{
    private Component1 _component1;
    private Component2 _component2;

    public ConcreteMediator(Component1 component1, Component2 component2)
    {
        _component1 = component1;
        _component1.SetMediator(this);
        _component2 = component2;
        _component2.SetMediator(this);
    }

    public void Notify(object sender, string ev)
    {
        if (ev == "A")
        {
            Console.WriteLine("Mediator reacts on A and triggers following operations:");
            this._component2.DoC();
        }
        if (ev == "D")
        {
            Console.WriteLine("Mediator reacts on D and triggers following operations:");
            this._component1.DoB();
            this._component2.DoC();
        }
    }
}

Next, we define a base component class and two concrete components:

public abstract class BaseComponent
{
    protected IMediator _mediator;

    public BaseComponent(IMediator mediator = null)
    {
        _mediator = mediator;
    }

    public void SetMediator(IMediator mediator)
    {
        this._mediator = mediator;
    }
}

// Concrete Components implement various functionality. They don't depend on other components.
// They also don't depend on any concrete Mediator class.
public class Component1 : BaseComponent
{
    public void DoA()
    {
        Console.WriteLine("Component 1 does A.");
        this._mediator.Notify(this, "A");
    }

    public void DoB()
    {
        Console.WriteLine("Component 1 does B.");
        this._mediator.Notify(this, "B");
    }
}

public class Component2 : BaseComponent
{
    public void DoC()
    {
        Console.WriteLine("Component 2 does C.");
        this._mediator.Notify(this, "C");
    }

    public void DoD()
    {
        Console.WriteLine("Component 2 does D.");
        this._mediator.Notify(this, "D");
    }
}

Finally, let's create client code:

class Program
{
    static void Main(string[] args)
    {
        // The client code.
        Component1 component1 = new Component1();
        Component2 component2 = new Component2();
        new ConcreteMediator(component1, component2);

        Console.WriteLine("Client triggers operation A.");
        component1.DoA();

        Console.WriteLine();

        Console.WriteLine("Client triggers operation D.");
        component2.DoD();
    }
}

In this example, components communicate through the mediator instead of directly, thereby reducing dependencies between components and making them easier to modify independently. When a component triggers an event (e.g., "Component 1 does A"), it notifies other components via the mediator, so they can respond accordingly (e.g., "Component 2 does C").

6. Memento Pattern

The Memento Pattern is a behavioral design pattern that allows you to save the state of an object so that it can be restored later. In most cases, this pattern enables saving and restoring historical states of an object without breaking encapsulation.

Below is a simple implementation of the Memento Pattern with three main classes: Originator (which holds an important state that may change over time), Memento (which stores a snapshot of the Originator's state), and Caretaker (which is responsible for saving Mementos).

// Originator class can generate a memento and restore its state from a memento.
public class Originator
{
    private string _state;

    public Originator(string state)
    {
        this._state = state;
        Console.WriteLine($"Originator: My initial state is: {_state}");
    }

    public void DoSomething()
    {
        Console.WriteLine("Originator: I'm doing something important.");
        _state = GenerateRandomString(30);
        Console.WriteLine($"Originator: and my state has changed to: {_state}");
    }

    private string GenerateRandomString(int length = 10)
    {
        string allowedSymbols = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
        string result = string.Empty;

        while (length > 0)
        {
            result += allowedSymbols[new Random().Next(0, allowedSymbols.Length)];

            length--;
        }

        return result;
    }

    public IMemento Save()
    {
        return new ConcreteMemento(_state);
    }

    public void Restore(IMemento memento)
    {
        _state = memento.GetState();
        Console.WriteLine($"Originator: My state has changed to: {_state}");
    }
}

// Memento interface provides methods to get the memento and originator state. However, not all methods are declared in this interface; some are only declared in the originator.
public interface IMemento
{
    string GetName();

    string GetState();

    DateTime GetDate();
}

// Concrete Memento stores the originator state and implements backup through the originator. Mementos are immutable, so there are no set methods.
public class ConcreteMemento : IMemento
{
    private string _state;
    private DateTime _date;

    public ConcreteMemento(string state)
    {
        _state = state;
        _date = DateTime.Now;
    }

    public string GetState()
    {
        return _state;
    }

    public string GetName()
    {
        return $"{_date} / ({_state.Substring(0, 9)})...";
    }

    public DateTime GetDate()
    {
        return _date;
    }
}

// Caretaker does not depend on the concrete memento class. Consequently, it has no access to the originator's state; it can only obtain the memento's metadata.
public class Caretaker
{
    private List<IMemento> _mementos = new List<IMemento>();
    private Originator _originator = null;

    public Caretaker(Originator originator)
    {
        this._originator = originator;
    }

    public void Backup()
    {
        Console.WriteLine("\nCaretaker: Saving Originator's state...");
        _mementos.Add(_originator.Save());
    }

    public void Undo()
    {
        if (_mementos.Count == 0)
        {
            return;
        }

        var memento = _mementos.Last();
        _mementos.Remove(memento);

        Console.WriteLine("Caretaker: Restoring state to: " + memento.GetName());
        try
        {
            _originator.Restore(memento);
        }
        catch (Exception)
        {
            Undo();
        }
    }

    public void ShowHistory()
    {
        Console.WriteLine("Caretaker: Here's the list of mementos:");

        foreach (var memento in _mementos)
        {
            Console.WriteLine(memento.GetName());
        }
    }
}

// Client code
class Program
{
    static void Main(string[] args)
    {
        Originator originator = new Originator("Super-duper-super-puper-super.");
        Caretaker caretaker = new Caretaker(originator);

        caretaker.Backup();
        originator.DoSomething();

        caretaker.Backup();
        originator.DoSomething();

        caretaker.Backup();
        originator.DoSomething();

        Console.WriteLine();
        caretaker.ShowHistory();

        Console.WriteLine("\nClient: Now, let's rollback!\n");
        caretaker.Undo();

        Console.WriteLine("\nClient: Once more!\n");
        caretaker.Undo();
    }
}

In the code above, Originator holds some important state and provides methods to save its state to a memento object and restore it from a memento. Caretaker is responsible for saving mementos but cannot operate on the state within the memento object. When the user performs an operation, we first save the current state, then execute the operation. If the user is not satisfied with the new state later, they can easily restore from the old memento.

7. Observer Pattern

The Observer Pattern is a behavioral design pattern where an object, called the subject, maintains a list of its dependents, called observers, and notifies them automatically of any state changes, usually by calling one of their methods. Below is a simple example of the Observer Pattern implemented in C#:

// Abstract Observer
public interface IObserver
{
    void Update();
}

// Concrete Observer
public class ConcreteObserver : IObserver
{
    private string name;

    public ConcreteObserver(string name)
    {
        this.name = name;
    }

    public void Update()
    {
        Console.WriteLine($"{name} received an update!");
    }
}

// Abstract Subject
public interface ISubject
{
    void RegisterObserver(IObserver observer);
    void RemoveObserver(IObserver observer);
    void NotifyObservers();
}

// Concrete Subject
public class ConcreteSubject : ISubject
{
    private List<IObserver> observers = new List<IObserver>();

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

    public void RemoveObserver(IObserver observer)
    {
        if (observers.Contains(observer))
        {
            observers.Remove(observer);
        }
    }

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

    public void ChangeState()
    {
        // Trigger state change, notify all observers
        NotifyObservers();
    }
}

In this code, IObserver is the abstract observer defining the Update method, and ConcreteObserver is the concrete observer implementing the IObserver interface. ISubject is the abstract subject defining RegisterObserver, RemoveObserver, and NotifyObservers methods, and ConcreteSubject is the concrete subject implementing the ISubject interface.

The following is an example of using this pattern:

class Program
{
    static void Main(string[] args)
    {
        ConcreteSubject subject = new ConcreteSubject();

        subject.RegisterObserver(new ConcreteObserver("Observer 1"));
        subject.RegisterObserver(new ConcreteObserver("Observer 2"));
        subject.RegisterObserver(new ConcreteObserver("Observer 3"));

        subject.ChangeState();

        Console.ReadLine();
    }
}

In this example, we create a ConcreteSubject object and register three observers. Then we change the subject's state via the ChangeState method, which triggers the subject to notify all observers.

Execution flow:

  1. Create a concrete subject object.
  2. Create several concrete observer objects and register them with the subject via the subject's RegisterObserver method.
  3. Change the subject's state via the ChangeState method, which triggers the subject to notify all observers.

8. State Pattern

In object-oriented programming, the State Pattern allows an object to alter its behavior when its internal state changes. This type of design pattern belongs to the behavioral pattern category. In the State pattern, we create objects representing various states and a context object whose behavior changes as its state changes.

Below is an example of the State Pattern. In this example, we will create a bank account with two states: NormalState and OverdrawnState. When the user performs operations (deposit and withdraw), the account state changes accordingly.

First, we define an interface representing a state:

public interface IAccountState
{
    void Deposit(Action addToBalance);
    void Withdraw(Action subtractFromBalance);
    void ComputeInterest();
}

Then, we create two classes representing concrete states:

public class NormalState : IAccountState
{
    public void Deposit(Action addToBalance)
    {
        addToBalance();
        Console.WriteLine("Deposit in NormalState");
    }

    public void Withdraw(Action subtractFromBalance)
    {
        subtractFromBalance();
        Console.WriteLine("Withdraw in NormalState");
    }

    public void ComputeInterest()
    {
        Console.WriteLine("Interest computed in NormalState");
    }
}

public class OverdrawnState : IAccountState
{
    public void Deposit(Action addToBalance)
    {
        addToBalance();
        Console.WriteLine("Deposit in OverdrawnState");
    }

    public void Withdraw(Action subtractFromBalance)
    {
        Console.WriteLine("No withdraw in OverdrawnState");
    }

    public void ComputeInterest()
    {
        Console.WriteLine("Interest and fees computed in OverdrawnState");
    }
}

Then, we create a Context class that uses these states to perform its tasks:

public class BankAccount
{
    private IAccountState _state;
    private double _balance;

    public BankAccount(IAccountState state)
    {
        _state = state;
        _balance = 0;
    }

    public void Deposit(double amount)
    {
        _state.Deposit(() => _balance += amount);
        StateChangeCheck();
    }

    public void Withdraw(double amount)
    {
        _state.Withdraw(() => _balance -= amount);
        StateChangeCheck();
    }

    public void ComputeInterest()
    {
        _state.ComputeInterest();
    }

    private void StateChangeCheck()
    {
        if (_balance < 0.0)
            _state = new OverdrawnState();
        else
            _state = new NormalState();
    }
}

Now, you can create an instance and run a Demo to test the State Pattern code:

public class Program
{
    public static void Main(string[] args)
    {
        var account = new BankAccount(new NormalState());

        account.Deposit(1000); // Deposit in NormalState
        account.Withdraw(2000); // Withdraw in NormalState; No withdraw in OverdrawnState
        account.Deposit(100); // Deposit in OverdrawnState

        account.ComputeInterest(); // Interest and fees computed in OverdrawnState

        Console.ReadKey();
    }
}

This program first performs deposit operations in the normal state, then attempts a withdrawal. Since the withdrawal amount exceeds the account balance, the account enters the overdrawn state and blocks further withdrawals. However, deposits are still allowed to bring the account back to normal. The behavior of computing interest also changes based on the account state.

9. Strategy Pattern

The Strategy Pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. Strategy lets the algorithm vary independently from clients that use it.

Below is a simple C# implementation of the Strategy Pattern. In this example, we will create sorting strategies, such as quick sort and bubble sort, that implement the same interface, and then create a Context class that uses these strategies to perform sorting.

First, we define an interface representing a sorting strategy:

public interface ISortStrategy
{
    void Sort(List<int> list);
}

Then, we create two classes representing concrete strategies:

public class QuickSort : ISortStrategy
{
    public void Sort(List<int> list)
    {
        list.Sort();  // Quick sort is in-place but here we are using built-in method
        Console.WriteLine("QuickSorted list ");
    }
}

public class BubbleSort : ISortStrategy
{
    public void Sort(List<int> list)
    {
        int n = list.Count;
        for (int i = 0; i < n - 1; i++)
            for (int j = 0; j < n - i - 1; j++)
                if (list[j] > list[j + 1])
                {
                    // swap temp and list[i]
                    int temp = list[j];
                    list[j] = list[j + 1];
                    list[j + 1] = temp;
                }

        Console.WriteLine("BubbleSorted list ");
    }
}

Then, we create a Context class that uses these strategies to perform its tasks:

public class SortedList
{
    private List<int> _list = new List<int>();
    private ISortStrategy _sortstrategy;

    public void SetSortStrategy(ISortStrategy sortstrategy)
    {
        this._sortstrategy = sortstrategy;
    }

    public void Add(int num)
    {
        _list.Add(num);
    }

    public void Sort()
    {
        _sortstrategy.Sort(_list);

        // Print sorted list
        foreach (int num in _list)
        {
            Console.Write(num + " ");
        }
        Console.WriteLine();
    }
}

Now, you can create an instance and run a Demo to test the Strategy Pattern code:

public class Program
{
    public static void Main(string[] args)
    {
        SortedList sortedList = new SortedList();

        sortedList.Add(1);
        sortedList.Add(5);
        sortedList.Add(3);
        sortedList.Add(4);
        sortedList.Add(2);

        sortedList.SetSortStrategy(new QuickSort());
        sortedList.Sort();  // Output: QuickSorted list 1 2 3 4 5

        sortedList.SetSortStrategy(new BubbleSort());
        sortedList.Sort();  // Output: BubbleSorted list 1 2 3 4 5

        Console.ReadKey();
    }
}

This program first creates an unsorted list, then sorts it first using the quick sort strategy, and then using the bubble sort strategy.

10. Template Method Pattern

The Template Method Pattern defines the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm's structure.

Below is an example of the Template Method Pattern. In this example, we will create a process for cooking food that has some fixed steps (e.g., preparing ingredients, cleaning up), but the specific cooking steps depend on the food.

First, we define an abstract template class:

public abstract class CookingProcedure
{
    // The 'Template method'
    public void PrepareDish()
    {
        PrepareIngredients();
        Cook();
        CleanUp();
    }

    public void PrepareIngredients()
    {
        Console.WriteLine("Preparing the ingredients...");
    }

    // These methods will be overridden by subclasses
    public abstract void Cook();

    public void CleanUp()
    {
        Console.WriteLine("Cleaning up...");
    }
}

Then, we create two concrete subclasses that implement the specific cooking steps:

public class CookPasta : CookingProcedure
{
    public override void Cook()
    {
        Console.WriteLine("Cooking pasta...");
    }
}

public class BakeCake : CookingProcedure
{
    public override void Cook()
    {
        Console.WriteLine("Baking cake...");
    }
}

Now, you can create an instance and run a Demo to test the Template Method Pattern code:

public class Program
{
    public static void Main(string[] args)
    {
        CookingProcedure cookingProcedure = new CookPasta();
        cookingProcedure.PrepareDish();

        Console.WriteLine();

        cookingProcedure = new BakeCake();
        cookingProcedure.PrepareDish();

        Console.ReadKey();
    }
}

In this program, we first create a CookPasta object and call its PrepareDish method. Then we create a BakeCake object and call its PrepareDish method again. Although these two objects have different Cook methods, the structure of their PrepareDish methods (i.e., the skeleton of the algorithm) is the same.

11. Visitor Pattern

The Visitor Pattern is a software design pattern that separates algorithms from the object structure on which they operate. The basic idea of this pattern is to change the operation of elements through so-called "visitors". In this way, the element classes can be used to represent the element structure, while the specific operations can be defined in the visitor classes.

Below is an example of the Visitor Pattern implemented in C#, including detailed comments and execution flow.

This example has three main parts: Visitor (IVisitor), Element (IElement), and ObjectStructure. Also, there are ConcreteVisitor and ConcreteElement.

// Visitor interface
public interface IVisitor
{
    void VisitConcreteElementA(ConcreteElementA concreteElementA);
    void VisitConcreteElementB(ConcreteElementB concreteElementB);
}

// Concrete Visitor A
public class ConcreteVisitorA : IVisitor
{
    public void VisitConcreteElementA(ConcreteElementA concreteElementA)
    {
        Console.WriteLine($"{concreteElementA.GetType().Name} is being visited by {this.GetType().Name}");
    }

    public void VisitConcreteElementB(ConcreteElementB concreteElementB)
    {
        Console.WriteLine($"{concreteElementB.GetType().Name} is being visited by {this.GetType().Name}");
    }
}

// Concrete Visitor B
public class ConcreteVisitorB : IVisitor
{
    public void VisitConcreteElementA(ConcreteElementA concreteElementA)
    {
        Console.WriteLine($"{concreteElementA.GetType().Name} is being visited by {this.GetType().Name}");
    }

    public void VisitConcreteElementB(ConcreteElementB concreteElementB)
    {
        Console.WriteLine($"{concreteElementB.GetType().Name} is being visited by {this.GetType().Name}");
    }
}

// Element interface
public interface IElement
{
    void Accept(IVisitor visitor);
}

// Concrete Element A
public class ConcreteElementA : IElement
{
    public void Accept(IVisitor visitor)
    {
        visitor.VisitConcreteElementA(this);
    }
}

// Concrete Element B
public class ConcreteElementB : IElement
{
    public void Accept(IVisitor visitor)
    {
        visitor.VisitConcreteElementB(this);
    }
}

// Object Structure
public class ObjectStructure
{
    private List<IElement> _elements = new List<IElement>();

    public void Attach(IElement element)
    {
        _elements.Add(element);
    }

    public void Detach(IElement element)
    {
        _elements.Remove(element);
    }

    public void Accept(IVisitor visitor)
    {
        foreach (var element in _elements)
        {
            element.Accept(visitor);
        }
    }
}

Execution flow:

  1. Create instances of concrete elements ConcreteElementA and ConcreteElementB.
  2. Create an instance of ObjectStructure and add the concrete elements created in step 1 to the object structure.
  3. Create instances of concrete visitors ConcreteVisitorA and ConcreteVisitorB.
  4. Call the Accept method of the object structure, passing the concrete visitors created in step 3, so that the concrete visitors visit all elements in the object structure.

The following is an example using the above code:

public class Program
{
    public static void Main()
    {
        ObjectStructure objectStructure = new ObjectStructure();

        objectStructure.Attach(new ConcreteElementA());
        objectStructure.Attach(new ConcreteElementB());

        ConcreteVisitorA visitorA = new ConcreteVisitorA();
        ConcreteVisitorB visitorB = new ConcreteVisitorB();

        objectStructure.Accept(visitorA);
        objectStructure.Accept(visitorB);

        Console.ReadKey();
    }
}

This program will print information showing that visitor A and visitor B visit concrete element A and concrete element B respectively.

Technical Exchange

.NET Core Discussion Group: 737776595

Shared by token

Keep Exploring

Related Reading

More Articles