Design Pattern with .NET Delegates

Design Pattern and Generic Delegates

In this article, I will discuss how to use generic delegates to implement design patterns. There are some design patterns that can be implemented using generic delegates. For example, the Strategy pattern can be implemented using a generic delegate. The Observer pattern can also be implemented using a generic delegate.

Strategy Pattern

Let's see how the Strategy pattern can be implemented using a generic delegate. The Strategy pattern is a behavioral design pattern that allows you to define a family of algorithms, encapsulate each one, and make them interchangeable. The Strategy pattern lets the algorithm vary independently from clients that use it.

Non-Generic Implementation

Below is a non-generic implementation of the Strategy pattern. The Strategy pattern is implemented using an interface that defines the strategy. Concrete strategies implement the strategy interface. A context class is used to set the strategy and execute it.

// Strategy interface
public interface IStrategy
{
    void Execute();
}

// Concrete strategies
public class StrategyA : IStrategy
{
    public void Execute()
    {
        Console.WriteLine("Executing Strategy A");
    }
}

public class StrategyB : IStrategy
{
    public void Execute()
    {
        Console.WriteLine("Executing Strategy B");
    }
}

// Context class
public class Context
{
    private IStrategy strategy;

    public void SetStrategy(IStrategy strategy)
    {
        this.strategy = strategy;
    }

    public void ExecuteStrategy()
    {
        strategy.Execute();
    }
}
// Usage
var context = new Context();

context.SetStrategy(new StrategyA());
context.ExecuteStrategy(); // Output: Executing Strategy A

context.SetStrategy(new StrategyB());
context.ExecuteStrategy(); // Output: Executing Strategy B`

Generic Delegate Implementation

Below is a generic delegate implementation of the Strategy pattern. The Strategy pattern is implemented using a generic delegate that defines the strategy. Concrete strategies implement the strategy delegate. A context class is used to set the strategy and execute it.


```csharp
// Context class
public class Context
{
    private Func<string> strategy;

    public void SetStrategy(Func<string> strategy)
    {
        this.strategy = strategy;
    }

    public void ExecuteStrategy()
    {
        var result = strategy();
        Console.WriteLine(result);
    }
}

// Usage
var context = new Context();

context.SetStrategy(() => "Executing Strategy A");
context.ExecuteStrategy(); // Output: Executing Strategy A

context.SetStrategy(() => "Executing Strategy B");
context.ExecuteStrategy(); // Output: Executing Strategy B`

Observer Pattern

Let's see how the Observer pattern can be implemented using a generic delegate. The Observer pattern is a behavioral design pattern that defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.

Non-Generic Implementation of the Observer Pattern

Below is a non-generic implementation of the Observer pattern. The Observer pattern is implemented using an interface that defines the observer. Concrete observers implement the observer interface. A subject class is used to attach, detach, and notify observers.

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

// Subject class
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();
        }
    }
}

// Concrete observers
public class ObserverA : IObserver
{
    public void Update()
    {
        Console.WriteLine("Observer A received update");
    }
}

public class ObserverB : IObserver
{
    public void Update()
    {
        Console.WriteLine("Observer B received update");
    }
}

// Usage
var subject = new Subject();
var observerA = new ObserverA();
var observerB = new ObserverB();

subject.Attach(observerA);
subject.Attach(observerB);

subject.Notify(); // Output: Observer A received update
                  //         Observer B received update

subject.Detach(observerA);

subject.Notify(); // Output: Observer B received update`

Generic Delegate Implementation of the Observer Pattern

Below is a generic delegate implementation of the Observer pattern. The Observer pattern is implemented using a generic delegate that defines the observer. Concrete observers implement the observer delegate. A subject class is used to attach, detach, and notify observers.

// Subject class
public class Subject
{
    private List<Func<string>> observers = new List<Func<string>>();

    public void Attach(Func<string> observer)
    {
        observers.Add(observer);
    }

    public void Detach(Func<string> observer)
    {
        observers.Remove(observer);
    }

    public void Notify()
    {
        foreach (var observer in observers)
        {
            var result = observer();
            Console.WriteLine(result);
        }
    }
}

// Usage
var subject = new Subject();

subject.Attach(() => "Observer A received update");
subject.Attach(() => "Observer B received update");

subject.Notify(); // Output: Observer A received update
                  //         Observer B received update

subject.Detach(() => "Observer A received update");

Command Design pattern

Let's see how the Command pattern can be implemented using a generic delegate. The Command pattern is a behavioral design pattern that encapsulates a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations.

Non-Generic Implementation of the Command Pattern

Below is a non-generic implementation of the Command pattern. The Command pattern is implemented using an interface that defines the command. Concrete commands implement the command interface. An invoker class is used to set the command and execute it.

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

// Concrete commands
public class CommandA : ICommand
{
    public void Execute()
    {
        Console.WriteLine("Executing Command A");
    }
}

public class CommandB : ICommand
{
    public void Execute()
    {
        Console.WriteLine("Executing Command B");
    }
}

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

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

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

// Usage
var invoker = new Invoker();

invoker.SetCommand(new CommandA());
invoker.ExecuteCommand(); // Output: Executing Command A

invoker.SetCommand(new CommandB());
invoker.ExecuteCommand(); // Output: Executing Command B

Generic Delegate Implementation of the Command Pattern

Below is a generic delegate implementation of the Command pattern. The Command pattern is implemented using a generic delegate that defines the command. Concrete commands implement the command delegate. An invoker class is used to set the command and execute it.


// Generic delegate representing the command
public delegate void CommandDelegate();

// Invoker class
public class Invoker
{
    private CommandDelegate command;

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

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

// Usage
var invoker = new Invoker();

invoker.SetCommand(() => Console.WriteLine("Executing Command A"));
invoker.ExecuteCommand(); // Output: Executing Command A

invoker.SetCommand(() => Console.WriteLine("Executing Command B"));
invoker.ExecuteCommand(); // Output: Executing Command B

Template Method Design Pattern

Let's see how the Template Method pattern can be implemented using a generic delegate. The Template Method pattern is a behavioral design pattern that defines the skeleton of an algorithm in a method, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm's structure.

Non-Generic Implementation of the Template Method Pattern

Below is a non-generic implementation of the Template Method pattern. The Template Method pattern is implemented using an abstract class that defines the template method. Concrete classes implement the template method.

// Abstract class defining the template method
public abstract class AbstractClass
{
    public void TemplateMethod(object data)
    {
        Step1(data); // Step 1: Execute the first operation
        Step2(data); // Step 2: Execute the second operation
    }

    protected abstract void Step1(object data);
    protected abstract void Step2(object data);
}

public class ConcreteClass : AbstractClass
{
    protected override void Step1(object data)
    {
        // Step 1 implementation
    }

    protected override void Step2(object data)
    {
        // Step 2 implementation
    }
}

public class Program
{
    static void Main()
    {
        var concreteObject = new ConcreteClass();
        concreteObject.TemplateMethod("Data");
    }
}

Generic Delegate Implementation of the Template Method Pattern

Below is a generic delegate implementation of the Template Method pattern. The Template Method pattern is implemented using a generic delegate that defines the template method. Concrete classes implement the template method.

public abstract class AbstractClass
{
    public void TemplateMethod<T>(T data, Action<T>? step1, Action<T>? step2)
    {
        if (step1 != null)
        {
            step1.Invoke(data); // Step 1: Execute the first operation if provided
        }
        else
        {
            Step1(data); // Step 1: Execute the default implementation if not provided
        }

        if (step2 != null)
        {
            step2.Invoke(data); // Step 2: Execute the second operation if provided
        }
        else
        {
            Step2(data); // Step 2: Execute the default implementation if not provided
        }
    }

    protected abstract void Step1<T>(T data);
    protected abstract void Step2<T>(T data);
}


public class ConcreteClass : AbstractClass
{
    protected override void Step1<T>(T data)
    {
        Console.WriteLine("Executing Step 1 with data: " + data);
        // Perform step 1 operation
    }

    protected override void Step2<T>(T data)
    {
        Console.WriteLine("Executing default Step 2 with data: " + data);
        // Perform default step 2 operation
    }
}

public class Program
{
    static void Main()
    {
        var concreteObject = new ConcreteClass();

        // Using a lambda expression for Step1 and null for Step2
        concreteObject.TemplateMethod("Data", data => Console.WriteLine("Custom Step 1 with data: " + data),
            null);

    }
}


Similar Articles