GoF Behavioral Design Patterns

GoF Behavioral Design Patterns

Software Architecture Simplified

ยท

29 min read

Featured on Hashnode

Table of contents

Introduction ๐Ÿ“‹

Design Pattern ๐Ÿงฑ

A design pattern is a solution that can be repeated/use every time that a specific problem/scenario occurs in the software design. The concept behind these solutions is independent of the programming language.

If we make an analogy with food, there are lots of different ways (recipes) to make French macarons, each one of them will create a different product with a different quality, so in this case, why not get the knowledge of the most experienced chef and follow his/her recipe and steps every time we need to make a French macaron?

image.png

This is the idea behind design patterns, to follow the best practices used by โ€œchefsโ€ (Gang of Four (GoF)) to tackle a specific problem.

Gang of Four (GoF) ๐Ÿ““

Gang of Four is the term that represents the four authors (Erich Gamma, Richard Helm, Ralph Johnson and John Vlissides) of the well-known book in the software industry: โ€œDesign Patterns: Elements of Reusable Object-Oriented Softwareโ€. This book was originally published in 1994 and introduced 23 solutions for commonly occurring design problems.

Gang of Four is the best book ever written on object-oriented design - possibly of any style of design. This book has been enormously influential on the software industry - just look at the Java and .NET libraries which are crawling with GOF patterns. Martin Fowler (software developer, author and international public speaker on software development)

image.png

Design Categories โœ”๏ธ

The 23 GoF Design Patterns are broken into three main categories:

โœ”๏ธBehavioral: Describes how objects interact/communicate between themselves. โœ”๏ธCreational: Describes how to instantiate an object without large and complex. โœ”๏ธStructural: Describes how objects/classes are composed to form larger structures.

image.png

In this article, letโ€™s analyze the design patterns that are part of the behavioral category.

Behavioral Patterns

  • Chain of Responsibility: A way of passing a request along a chain of objects
  • Command: Encapsulate a command request as an object
  • Interpreter: A way to include language elements in a program
  • Iterator: Sequentially access the elements of a collection
  • Mediator: Defines simplified communication between classes
  • Memento: Capture and restore an objectโ€™s internal state
  • Observer: A way of notifying change to a number of classes
  • State: Alter an objectโ€™s behaviour when its state changes
  • Strategy: Encapsulates an algorithm to a subclass
  • Template Method: Defer the exact steps of an algorithm to a subclass
  • Visitor: Defines a new operation to a class without change

Source Code ๐ŸŽฒ

github.com/VictorLins/DesignPatterns


Behavioral Patterns

1. Chain of Responsability

Objective ๐ŸŽฏ

Allow a single request to be processed by multiple handlers in the chain. Each handler will decide either to process the request or to pass it to the next handler in the chain.

image.png

UML ๐Ÿ“

image.png

Participants ๐Ÿ”—

โ€ข Handler:

  • Defines an interface for handling the requests

โ€ข Concrete Handler:

  • Implements Handler interface
  • Handles the request, either processing it or passing it to the successor handler

โ€ข Client:

  • Initiates the flow by forwarding the request to a Concrete Handler object

Sample Code ๐ŸŽฎ

Structural Example ๐Ÿ›๏ธ

image.png

public static class ChainOfResponsibilityStructural
    {
        public static void Execute()
        {
            Handler lConcreteHandler1 = new ConcreteHandler1();
            Handler lConcreteHandler2 = new ConcreteHandler2();
            Handler lConcreteHandler3 = new ConcreteHandler3();

            lConcreteHandler1.SetSuccessor(lConcreteHandler2);
            lConcreteHandler2.SetSuccessor(lConcreteHandler3);

            lConcreteHandler1.HandleRequest("AAA");
            lConcreteHandler1.HandleRequest("BBB");
        }
    }

    public abstract class Handler
    {
        protected Handler _SuccessorHandler;
        public void SetSuccessor(Handler prSuccessorHandler)
        {
            _SuccessorHandler = prSuccessorHandler;
        }
        public abstract void HandleRequest(string prRequest);
    }

    public class ConcreteHandler1 : Handler
    {
        public override void HandleRequest(string prRequest)
        {
            Console.WriteLine("ConcreteHandler1 - Handling Request: " + prRequest);

            if (_SuccessorHandler != null)
                _SuccessorHandler.HandleRequest(prRequest);
        }
    }

    public class ConcreteHandler2 : Handler
    {
        public override void HandleRequest(string prRequest)
        {
            Console.WriteLine("ConcreteHandler2 - Handling Request: " + prRequest);

            if (_SuccessorHandler != null)
                _SuccessorHandler.HandleRequest(prRequest);
        }
    }

    public class ConcreteHandler3 : Handler
    {
        public override void HandleRequest(string prRequest)
        {
            Console.WriteLine("ConcreteHandler3 - Handling Request: " + prRequest);

            if (_SuccessorHandler != null)
                _SuccessorHandler.HandleRequest(prRequest);
        }
    }

Output

image.png

Real-world Example ๐Ÿ”ฅ

image.png

public static class ChainOfResponsibilityPractical
    {
        public static void Execute()
        {
            Approver lAssistantManager = new AssistantManager();
            Approver lManager = new Manager();
            Approver lDirector = new Director();

            lAssistantManager.SetSuccessor(lManager);
            lManager.SetSuccessor(lDirector);

            Console.WriteLine("---------------------------------------------------------------");
            Console.WriteLine("Requesting approval for a order of cost USD 50.");
            lAssistantManager.ApproveOrder(50);
            Console.WriteLine("---------------------------------------------------------------");
            Console.WriteLine("Requesting approval for a order of cost USD 250.");
            lAssistantManager.ApproveOrder(250);
            Console.WriteLine("---------------------------------------------------------------");
            Console.WriteLine("Requesting approval for a order of cost USD 500.");
            lAssistantManager.ApproveOrder(500);
            Console.WriteLine("---------------------------------------------------------------");
            Console.WriteLine("Requesting approval for a order of cost USD 600.");
            lAssistantManager.ApproveOrder(600);
        }
    }

    public abstract class Approver
    {
        protected Approver _SuccessorApprover;
        public void SetSuccessor(Approver prApprover)
        {
            _SuccessorApprover = prApprover;
        }
        public abstract void ApproveOrder(int prCost);
    }

    public class AssistantManager : Approver
    {
        public override void ApproveOrder(int prCost)
        {
            if (prCost <= 50)
                Console.WriteLine("Assistant Manager - Order Approved. Cost: USD" + prCost);
            else
            {
                Console.WriteLine("Assistant Manager - Can't approve (cost > 50), sending to next approver in the hierarchy.");

                if (_SuccessorApprover != null)
                    _SuccessorApprover.ApproveOrder(prCost);
            }
        }
    }

    public class Manager : Approver
    {
        public override void ApproveOrder(int prCost)
        {
            if (prCost <= 250)
                Console.WriteLine("Manager - Order Approved. Cost: USD" + prCost);
            else
            {
                Console.WriteLine("Manager - Can't approve (cost > 250), sending to next approver in the hierarchy.");

                if (_SuccessorApprover != null)
                    _SuccessorApprover.ApproveOrder(prCost);
            }
        }
    }

    public class Director : Approver
    {
        public override void ApproveOrder(int prCost)
        {
            if (prCost <= 500)
                Console.WriteLine("Director - Order Approved. Cost: USD" + prCost);
            else
                Console.WriteLine("Director - Not approved, cost is too high");
        }
    }

Output

image.png

2. Command

Objective ๐ŸŽฏ

Transform a request in a stand-alone object allowing to pass it as a method argument, delay or queue its execution also providing an interface to undo what was done.

UML ๐Ÿ“

image.png

Participants ๐Ÿ”—

โ€ข Command:

  • Declares an interface for executing an operation

โ€ข ConcreteCommand:

  • Represents the binding between a Receiver object and an action
  • Implements the method Execute by invoking the corresponding operation(s) on Receiver

โ€ข Invoker:

  • Asks to the Command object to execute the request

โ€ข Receiver:

  • Knows how to perform the operations associated with the request

Sample Code ๐ŸŽฎ

Structural Example ๐Ÿ›๏ธ

image.png

public static class CommandStructural
    {
        public static void Execute()
        {
            Receiver lReceiver = new Receiver();
            Command lCommand = new ConcreteCommand(lReceiver);
            Invoker lInvoker = new Invoker();

            lInvoker.SetCommand(lCommand);
            lInvoker.ExecuteCommand();
        }
    }

    public abstract class Command
    {
        protected Receiver _Receiver;

        public Command(Receiver prReceiver)
        {
            _Receiver = prReceiver;
        }

        public abstract void Execute();
    }

    public class ConcreteCommand : Command
    {
        public ConcreteCommand(Receiver prReceiver) :
            base(prReceiver)
        {
        }

        public override void Execute()
        {
            _Receiver.Action();
        }
    }

    public class Receiver
    {
        public void Action()
        {
            Console.WriteLine("Called Receiver.Action()");
        }
    }

    public class Invoker
    {
        private Command _Command;

        public void SetCommand(Command prCommand)
        {
            _Command = prCommand;
        }

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

Output

image.png

Real-world Example ๐Ÿ”ฅ

image.png

public static class CommandPractical
    {
        public static void Execute()
        {
            User lUser = new User();

            // User presses calculator buttons
            lUser.Compute('+', 100);
            lUser.Compute('-', 50);
            lUser.Compute('*', 10);
            lUser.Compute('/', 2);

            // Undo 4 commands
            lUser.Undo(4);

            // Redo 3 commands
            lUser.Redo(3);
        }
    }

    public abstract class Operation
    {
        public abstract void Execute();
        public abstract void UnExecute();
    }

    class CalculatorCommand : Operation
    {
        private char _Operator;
        private int _Operand;
        private Calculator _Calculator;

        public CalculatorCommand(Calculator prCalculator,
            char prOperator, int prOperand)
        {
            _Calculator = prCalculator;
            _Operator = prOperator;
            _Operand = prOperand;
        }

        public char Operator
        {
            set { _Operator = value; }
        }

        public int Operand
        {
            set { _Operand = value; }
        }

        public override void Execute()
        {
            _Calculator.Operation(_Operator, _Operand);
        }

        public override void UnExecute()
        {
            _Calculator.Operation(Undo(_Operator), _Operand);
        }

        private char Undo(char prOperator)
        {
            switch (prOperator)
            {
                case '+': return '-';
                case '-': return '+';
                case '*': return '/';
                case '/': return '*';
                default: throw new ArgumentException(prOperator.ToString());
            }
        }
    }

    public class Calculator
    {
        private int _CurrentValue = 0;

        public void Operation(char prOperator, int prOperand)
        {
            int lValueBefore = _CurrentValue;

            switch (prOperator)
            {
                case '+': _CurrentValue += prOperand; break;
                case '-': _CurrentValue -= prOperand; break;
                case '*': _CurrentValue *= prOperand; break;
                case '/': _CurrentValue /= prOperand; break;
            }

            Console.WriteLine($"Operation: {lValueBefore} {prOperator} {prOperand} - Result = {_CurrentValue}");
        }
    }

    public class User
    {
        private Calculator _Calculator = new Calculator();
        private List<Operation> _PerformedCommands = new List<Operation>();
        private int _CurrentOperation = 0;

        public void Redo(int prLevels)
        {
            Console.WriteLine("\n---- Redo {0} levels ", prLevels);

            for (int i = 0; i < prLevels; i++)
            {
                if (_CurrentOperation < _PerformedCommands.Count - 1)
                {
                    Operation lOperation = _PerformedCommands[_CurrentOperation];
                    lOperation.Execute();
                    _CurrentOperation++;
                }
            }
        }

        public void Undo(int prLevels)
        {
            Console.WriteLine("\n---- Undo {0} levels ", prLevels);

            for (int i = 0; i < prLevels; i++)
            {
                if (_CurrentOperation > 0)
                {
                    _CurrentOperation--;
                    Operation lOperation = _PerformedCommands[_CurrentOperation] as Operation;
                    lOperation.UnExecute();
                }
            }
        }

        public void Compute(char prOperator, int prOperand)
        {
            Operation lCommand = new CalculatorCommand(_Calculator, prOperator, prOperand);
            lCommand.Execute();

            _PerformedCommands.Add(lCommand);
            _CurrentOperation++;
        }
    }

Output

image.png

3. Interpreter

Objective ๐ŸŽฏ

Provide a way to interpret/evaluate a language grammar or expression.

UML ๐Ÿ“

image.png

Participants ๐Ÿ”—

โ€ข AbstractExpression:

  • Declares an Evaluate() operation that all nodes (leaf and non-leaf) must override

โ€ข Terminal Expression:

  • Implements a concrete operation

โ€ข Nonterminal Expression:

  • Implements an operation and execute the next expression

โ€ข Interpreter:

  • Defines an interface to receive the Context
  • Based on the Context (userโ€™s input) will create the Expression classes
  • Will interpret the userโ€™s input (Context) by executing the Evaluate method of the Expression classes

โ€ข Client:

  • Will receive the userโ€™s input data
  • Responsible to create and maintain the Context
  • Will instantiate an Interpreter object and call the method Interpret passing the Context as argument

Notes ๐Ÿ“

โ€ข This pattern is recommended for relatively simple grammar interpretation, which doesn't need to evolve and extend much.

โ€ข Terminal Expressions are considered the leaf nodes

โ€ข Non-Terminal Expressions are considered the non-leaf nodes

image.png

โ€ข The example below illustrates how this pattern can be used to interpret SQL statements

image.png

Sample Code ๐ŸŽฎ

Structural Example ๐Ÿ›๏ธ

image.png

public static class InterpreterStructural
    {
        // Client
        public static void Execute()
        {
            Context lContext = new Context();
            lContext._UserInput = "AABBCC";

            Interpreter lInterpreter = new Interpreter();
            lInterpreter.Interprete(lContext);
        }
    }

    public class Interpreter
    {
        public void Interprete(Context prContext)
        {
            List<Expression> _Expressions = new List<Expression>();
            _Expressions.Add(new NonTerminalExpression());
            _Expressions.Add(new TerminalExpression());

            foreach (Expression lExpressionCurrent in _Expressions)
            {
                lExpressionCurrent.Evaluate(prContext);
            }
        }
    }

    public abstract class Expression
    {
        public abstract void Evaluate(Context prContext);
    }

    public class NonTerminalExpression : Expression
    {
        public override void Evaluate(Context prContext)
        {
            Console.WriteLine($"--Executing a Non-Terminal Operation with the input \"{prContext._UserInput}\"");
        }
    }

    public class TerminalExpression : Expression
    {
        public override void Evaluate(Context prContext)
        {
            Console.WriteLine($"----Executing a Terminal Operation with the input \"{prContext._UserInput}\"");
        }
    }

    public class Context
    {
        public string _UserInput { get; set; }
    }

Output

image.png

Real-world Example ๐Ÿ”ฅ

image.png

public static class InterpreterPractical
    {
        // Client
        public static void Execute()
        {
            CommandLineContext lCommandLineContext = new CommandLineContext();
            lCommandLineContext._UserExpression = " Lorem Ipsum Dolor Sit Amet, Consectetur -u -t";

            CommandLineInterpreter lCommandLineInterpreter = new CommandLineInterpreter();
            lCommandLineInterpreter.Interprete(lCommandLineContext);

            lCommandLineContext._UserExpression = " Lorem Ipsum Dolor Sit Amet, Consectetur -l";
            lCommandLineInterpreter.Interprete(lCommandLineContext);
        }
    }

    public class CommandLineInterpreter
    {
        public void Interprete(CommandLineContext prCommandLineContext)
        {
            List<CommandLineExpression> _CommandLineExpressions = new List<CommandLineExpression>();

            // GET EXPRESSIONS
            List<string> lExpressions = prCommandLineContext._UserExpression.Substring(prCommandLineContext._UserExpression.IndexOf("-")).Split(" ").ToList();

            prCommandLineContext._Result = prCommandLineContext._UserExpression.Substring(0, prCommandLineContext._UserExpression.IndexOf("-"));

            foreach (string lExpressionCUrrent in lExpressions)
            {
                switch (lExpressionCUrrent)
                {
                    case ("-u"): _CommandLineExpressions.Add(new ToUpperCaseExpression()); break;
                    case ("-l"): _CommandLineExpressions.Add(new ToLowerCaseExpression()); break;
                    case ("-t"): _CommandLineExpressions.Add(new TrimExpression()); break;
                    default: break;
                }
            }

            // EXECUTE EXPRESSIONS
            Console.WriteLine($"\n\r- INTERPRETING INPUT: \"{prCommandLineContext._Result}\"");
            foreach (CommandLineExpression lCommandLineExpressionCurrent in _CommandLineExpressions)
            {
                lCommandLineExpressionCurrent.Evaluate(prCommandLineContext);
                Console.WriteLine($"-- {lCommandLineExpressionCurrent.GetType().Name} Executed - Result: \"{prCommandLineContext._Result}\"");
            }
            Console.WriteLine($"- INTERPRETATION RESULT: \"{prCommandLineContext._Result}\"");
        }
    }

    public abstract class CommandLineExpression
    {
        public abstract void Evaluate(CommandLineContext prCommandLineContext);
    }

    public class ToUpperCaseExpression : CommandLineExpression
    {
        public override void Evaluate(CommandLineContext prCommandLineContext)
        {
            prCommandLineContext._Result = prCommandLineContext._Result.ToUpper();
        }
    }

    public class ToLowerCaseExpression : CommandLineExpression
    {
        public override void Evaluate(CommandLineContext prCommandLineContext)
        {
            prCommandLineContext._Result = prCommandLineContext._Result.ToLower();
        }
    }

    public class TrimExpression : CommandLineExpression
    {
        public override void Evaluate(CommandLineContext prCommandLineContext)
        {
            prCommandLineContext._Result = prCommandLineContext._Result.Trim();
        }
    }

    public class CommandLineContext
    {
        public string _UserExpression { get; set; }
        public string _Result { get; set; }
    }

Output

image.png

4. Iterator

Objective ๐ŸŽฏ

Provide a way to access elements of a list sequentially without exposing its underlying representation.

UML ๐Ÿ“

image.png

Participants ๐Ÿ”—

โ€ข Iterator:

  • Defines an interface to access elements in the collection

โ€ข ConcreteIterator:

  • Implements the Iterator interface
  • Keeps track of the current position in the traversal of the collection

โ€ข Aggregate:

  • Defines the interface of the Iterator object that will manipulate the collection

โ€ข ConcreteAggregate:

  • Responsible for maintaining the real list of objects that will be manipulated
  • Implements the Aggregate interface and its method to return a ConcreteIterator

Notes ๐Ÿ“

โ€ข In the ConcreteAggregate object, an Indexer must be implemented to avoid external manipulation to the list items.

Sample Code ๐ŸŽฎ

Structural Example ๐Ÿ›๏ธ

image.png

public static class IteratorStructural
    {
        public static void Execute()
        {
            ConcreteAggregate lConcreteAggregate = new ConcreteAggregate();
            lConcreteAggregate[0] = "Item A";
            lConcreteAggregate[1] = "Item B";
            lConcreteAggregate[2] = "Item C";
            lConcreteAggregate[3] = "Item D";

            // Create Iterator and provide aggregate
            ConcreteIterator lConcreteIterator = new ConcreteIterator(lConcreteAggregate);

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

            object lobject = lConcreteIterator.First();
            while (lobject != null)
            {
                Console.WriteLine(lobject);
                lobject = lConcreteIterator.Next();
            }
        }
    }

    public abstract class Aggregate
    {
        public abstract Iterator CreateIterator();
    }

    public class ConcreteAggregate : Aggregate
    {
        private ArrayList _Items = new ArrayList();

        public override Iterator CreateIterator()
        {
            return new ConcreteIterator(this);
        }

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

        public object this[int prIndex]
        {
            get { return _Items[prIndex]; }
            set { _Items.Insert(prIndex, value); }
        }
    }

    public abstract class Iterator
    {
        public abstract object First();
        public abstract object Next();
        public abstract bool IsDone();
        public abstract object CurrentItem();
    }

    public class ConcreteIterator : Iterator
    {
        private ConcreteAggregate _ConcreteAggregate;
        private int _CurrentElement = 0;

        public ConcreteIterator(ConcreteAggregate prConcreteAggregate)
        {
            _ConcreteAggregate = prConcreteAggregate;
        }

        public override object First()
        {
            return _ConcreteAggregate[0];
        }

        public override object Next()
        {
            object lResult = null;
            if (_CurrentElement < _ConcreteAggregate.Count - 1)
            {
                lResult = _ConcreteAggregate[++_CurrentElement];
            }

            return lResult;
        }

        public override object CurrentItem()
        {
            return _ConcreteAggregate[_CurrentElement];
        }

        public override bool IsDone()
        {
            return _CurrentElement >= _ConcreteAggregate.Count;
        }
    }

Output

image.png

Real-world Example ๐Ÿ”ฅ

image.png

public static class IteratorPractical
    {
        public static void Execute()
        {
            OfficeFiles lOfficeFiles = new OfficeFiles();
            lOfficeFiles[0] = new File() { Name = "File001_PB", IsPrivate = false };
            lOfficeFiles[1] = new File() { Name = "File002_PV", IsPrivate = true };
            lOfficeFiles[2] = new File() { Name = "File003_PB", IsPrivate = false };
            lOfficeFiles[3] = new File() { Name = "File004_PV", IsPrivate = true };
            lOfficeFiles[4] = new File() { Name = "File005_PB", IsPrivate = false };
            lOfficeFiles[5] = new File() { Name = "File006_PV", IsPrivate = true };

            FileIterator lFileIterator = lOfficeFiles.CreateIterator();
            Console.WriteLine("Iterating over files using \"Public File Iterator\": ");

            File lFile = lFileIterator.First();
            Console.WriteLine("First File: " + lFile.Name);

            lFile = lFileIterator.Next();
            Console.WriteLine("Next File: " + lFile.Name);

            List<File> lFiles = lFileIterator.GetAll();
            Console.WriteLine("All Files: " + String.Join(", ", lFiles.Select(X => X.Name).ToList()));

            lFileIterator = new PrivateFileIterator(lOfficeFiles);
            Console.WriteLine("\n\rIterating over files using \"Private File Iterator\": ");

            lFile = lFileIterator.First();
            Console.WriteLine("First File: " + lFile.Name);

            lFile = lFileIterator.Next();
            Console.WriteLine("Next File: " + lFile.Name);

            lFiles = lFileIterator.GetAll();
            Console.WriteLine("All Files: " + String.Join(", ", lFiles.Select(X => X.Name).ToList()));
        }
    }

    // Aggregate (Concrete)
    public class OfficeFiles
    {
        private ArrayList _Items = new ArrayList();

        public FileIterator CreateIterator()
        {
            return new PublicFileIterator(this);
        }

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

        public object this[int prIndex]
        {
            get { return _Items[prIndex]; }
            set { _Items.Insert(prIndex, value); }
        }
    }

    // Iterator
    public abstract class FileIterator
    {
        public abstract File First();
        public abstract File Next();
        public abstract List<File> GetAll();
        public abstract File CurrentItem();
    }

    // Concrete Iterator
    public class PublicFileIterator : FileIterator
    {
        private OfficeFiles _OfficeFiles;
        private int _CurrentElement = 0;

        public PublicFileIterator(OfficeFiles prOfficeFiles)
        {
            _OfficeFiles = prOfficeFiles;
            this.First(); // To set the correct _CurrentElement
        }

        public override File First()
        {
            for (int i = 0; i < _OfficeFiles.Count; i++)
            {
                if (((File)_OfficeFiles[i]).IsPrivate == false)
                {
                    _CurrentElement = i;
                    return ((File)_OfficeFiles[_CurrentElement]);
                }

            }
            return null;
        }

        public override File Next()
        {
            object lResult = null;

            for (int i = _CurrentElement; i < _OfficeFiles.Count; i++)
            {
                _CurrentElement++;
                if (((File)_OfficeFiles[_CurrentElement]).IsPrivate == false)
                    return ((File)_OfficeFiles[_CurrentElement]);
            }

            return null;
        }

        public override List<File> GetAll()
        {
            List<File> lResult = new List<File>();

            for (int i = 0; i < _OfficeFiles.Count; i++)
            {
                if (((File)_OfficeFiles[i]).IsPrivate == false)
                    lResult.Add(((File)_OfficeFiles[i]));
            }

            return lResult;
        }

        public override File CurrentItem()
        {
            return ((File)_OfficeFiles[_CurrentElement]);
        }
    }

    // Concrete Iterator
    public class PrivateFileIterator : FileIterator
    {
        private OfficeFiles _OfficeFiles;
        private int _CurrentElement = 0;

        public PrivateFileIterator(OfficeFiles prOfficeFiles)
        {
            _OfficeFiles = prOfficeFiles;
            _OfficeFiles = prOfficeFiles;
            this.First(); // To set the correct _CurrentElement
        }

        public override File First()
        {
            for (int i = 0; i < _OfficeFiles.Count; i++)
            {
                if (((File)_OfficeFiles[i]).IsPrivate == true)
                {
                    _CurrentElement = i;
                    return ((File)_OfficeFiles[_CurrentElement]);
                }

            }
            return null;
        }

        public override File Next()
        {
            object lResult = null;

            for (int i = _CurrentElement; i < _OfficeFiles.Count; i++)
            {
                _CurrentElement++;
                if (((File)_OfficeFiles[_CurrentElement]).IsPrivate == true)
                    return ((File)_OfficeFiles[_CurrentElement]);
            }

            return null;
        }

        public override List<File> GetAll()
        {
            List<File> lResult = new List<File>();

            for (int i = 0; i < _OfficeFiles.Count; i++)
            {
                if (((File)_OfficeFiles[i]).IsPrivate == true)
                    lResult.Add(((File)_OfficeFiles[i]));
            }

            return lResult;
        }

        public override File CurrentItem()
        {
            return ((File)_OfficeFiles[_CurrentElement]);
        }
    }

    public class File
    {
        public string Name { get; set; }
        public bool IsPrivate { get; set; }
    }

Output

image.png

5. Mediator

Objective ๐ŸŽฏ

Reduce chaotic dependencies between objects by restricting direct communication and forcing objects to collaborate only via a mediator object.

UML ๐Ÿ“

image.png

Participants ๐Ÿ”—

โ€ข Mediator:

  • Defines an interface for communicating with Colleague objects

โ€ข ConcreteMediator:

  • Knows and maintains its colleagues
  • Coordinate Colleague objects

โ€ข Colleague:

  • Knows its Mediator object
  • Use Mediator object to communicate with other Colleague object

Sample Code ๐ŸŽฎ

Structural Example ๐Ÿ›๏ธ

image.png

public static class MediatorStructural
    {
        public static void Execute()
        {
            ConcreteMediator lConcreteMediator = new ConcreteMediator();

            ConcreteColleague1 lConcreteColleague1 = new ConcreteColleague1(lConcreteMediator);
            ConcreteColleague2 lConcreteColleague2 = new ConcreteColleague2(lConcreteMediator);

            lConcreteMediator.Colleague1 = lConcreteColleague1;
            lConcreteMediator.Colleague2 = lConcreteColleague2;

            lConcreteColleague1.Send("How are you?");
            lConcreteColleague2.Send("Fine, thanks");
        }
    }

    public abstract class Mediator
    {
        public abstract void Send(string message,
            Colleague colleague);
    }

    public class ConcreteMediator : Mediator
    {
        private ConcreteColleague1 _ConcreteColleague1;
        private ConcreteColleague2 _ConcreteColleague2;

        public ConcreteColleague1 Colleague1
        {
            set { _ConcreteColleague1 = value; }
        }

        public ConcreteColleague2 Colleague2
        {
            set { _ConcreteColleague2 = value; }
        }

        public override void Send(string prMessage, Colleague prColleague)
        {
            if (prColleague == _ConcreteColleague1)
            {
                Console.WriteLine("Mediator is sending message to Colleague2");
                _ConcreteColleague2.Notify(prMessage);
            }
            else
            {
                Console.WriteLine("Mediator is sending message to Colleague1");
                _ConcreteColleague1.Notify(prMessage);
            }
        }
    }

    public abstract class Colleague
    {
        protected Mediator _Mediator;

        public Colleague(Mediator prMediator)
        {
            _Mediator = prMediator;
        }
    }

    public class ConcreteColleague1 : Colleague
    {
        public ConcreteColleague1(Mediator prMediator)
            : base(prMediator) { }

        public void Send(string prMessage)
        {
            _Mediator.Send(prMessage, this);
        }

        public void Notify(string prMessage)
        {
            Console.WriteLine("Colleague1 gets message \"" + prMessage + "\"");
        }
    }

    public class ConcreteColleague2 : Colleague
    {
        public ConcreteColleague2(Mediator prMediator)
            : base(prMediator) { }

        public void Send(string prMessage)
        {
            _Mediator.Send(prMessage, this);
        }

        public void Notify(string prMessage)
        {
            Console.WriteLine("Colleague2 gets message: " + prMessage);
        }
    }

Output

image.png

Real-world Example ๐Ÿ”ฅ

image.png

public static class MediatorPractical
    {
        public static void Execute()
        {
            Chatroom lChatroom = new Chatroom();

            // Create participants
            Participant lGeorge = new Teacher("George");
            Participant lPaul = new Studant("Paul");
            Participant lRingo = new Studant("Ringo");
            Participant lJohn = new Studant("John");

            // Register them
            lChatroom.Register(lGeorge);
            lChatroom.Register(lPaul);
            lChatroom.Register(lRingo);
            lChatroom.Register(lJohn);

            // Chatting participants
            lPaul.Send("George", "Hi Teacher!");
            lGeorge.Send("Paul", "Hi Paul, how are you?");
            lPaul.Send("George", "I am good thanks");
            lGeorge.Send("John", "John, did you finish your homework?");
            lJohn.Send("George", "Hi Teacher, yes I did");
            lGeorge.Send("Ringo", "Ringo, what about you?");
            lRingo.Send("George", "Hi Teacher, I finished as well");
        }
    }

    public abstract class AbstractChatroom
    {
        public abstract void Register(Participant prParticipant);
        public abstract void Send(string prFrom, string prTo, string prMessage);
    }

    public class Chatroom : AbstractChatroom
    {
        private Dictionary<string, Participant> _Participants = new Dictionary<string, Participant>();

        public override void Register(Participant prParticipant)
        {
            if (!_Participants.ContainsValue(prParticipant))
                _Participants[prParticipant._Name] = prParticipant;

            prParticipant._Chatroom = this;
        }

        public override void Send(string prFrom, string prTo, string prMessage)
        {
            Participant lParticipant = _Participants[prTo];

            if (lParticipant != null)
                lParticipant.Receive(prFrom, prMessage);
        }
    }

    public class Participant
    {
        public Chatroom _Chatroom { get; set; }
        public string _Name { get; set; }

        public Participant(string prName)
        {
            _Name = prName;
        }

        public void Send(string prTo, string prMessage)
        {
            _Chatroom.Send(_Name, prTo, prMessage);
        }

        public virtual void Receive(string prFrom, string prMessage)
        {
            Console.WriteLine("{0} to {1}: '{2}'", prFrom, _Name, prMessage);
        }
    }

    public class Teacher : Participant
    {
        public Teacher(string prName) : base(prName) { }

        public override void Receive(string prFrom, string prMessage)
        {
            Console.Write("To a Teacher: ");
            base.Receive(prFrom, prMessage);
        }
    }

    public class Studant : Participant
    {
        public Studant(string prName) : base(prName) { }

        public override void Receive(string prFrom, string prMessage)
        {
            Console.Write("To a Studant: ");
            base.Receive(prFrom, prMessage);
        }
    }

Output

image.png

6. Memento

Objective ๐ŸŽฏ

Allow to save and restore a previous state of an object without violating encapsulation (not revealing details of its implementation).

UML ๐Ÿ“

image.png

Participants ๐Ÿ”—

โ€ข Memento:

  • Stores the internal state of the Originator object

โ€ข Originator:

  • Creates a memento object containing a snapshot of its current internal state
  • Uses memento to restore its internal state

โ€ข Caretaker:

  • Responsible for the mementoโ€™s safekeeping

Sample Code ๐ŸŽฎ

Structural Example ๐Ÿ›๏ธ

image.png

public static class MementoStructural
    {
        public static void Execute()
        {
            Originator lOriginator = new Originator();
            lOriginator.State = "On";

            // Store internal state
            Caretaker lCaretaker = new Caretaker();
            lCaretaker._Memento = lOriginator.CreateMemento();

            // Continue changing originator
            lOriginator.State = "Off";

            // Restore saved state
            lOriginator.SetMemento(lCaretaker._Memento);
        }
    }

    public class Originator
    {
        private string _State;

        public string State
        {
            get { return _State; }
            set
            {
                Console.WriteLine($"Originator - Changing state from \"{_State}\" to \"{value}\"");
                _State = value;
            }
        }

        public Memento CreateMemento()
        {
            Console.WriteLine($"Originator - Creating memento of the current state \"{_State}\"");
            return (new Memento(_State));
        }

        public void SetMemento(Memento prMemento)
        {
            Console.WriteLine($"Originator - Restoring state...");
            State = prMemento._State;
        }
    }

    public class Memento
    {
        public string _State { get; set; }

        public Memento(string prState)
        {
            _State = prState;
        }
    }

    public class Caretaker
    {
        public Memento _Memento { get; set; }
    }

Output

image.png

Real-world Example ๐Ÿ”ฅ

image.png

public static class MementoPractical
    {
        public static void Execute()
        {
            WordDocument lWordDocument = new WordDocument();
            DocumentCaretaker lDocumentCaretaker = new DocumentCaretaker();

            lWordDocument.Content = "Content ABC";
            lWordDocument.SaveContentState(lDocumentCaretaker);

            lWordDocument.Content = "Content ABCDEFG";
            lWordDocument.RevertToLastSave(lDocumentCaretaker);

            lWordDocument.Content = "Content 123";
            lWordDocument.RevertToLastSave(lDocumentCaretaker);
        }
    }

    public class WordDocument
    {
        private string _Content;

        public string Content
        {
            get { return _Content; }
            set
            {
                Console.WriteLine($"WordDocument - Changing content from \"{_Content}\" to \"{value}\"");
                _Content = value;
            }
        }

        public void SaveContentState(DocumentCaretaker prDocumentCaretaker)
        {
            Console.WriteLine($"WordDocument - Saving current content");
            prDocumentCaretaker._DocumentMemento = new DocumentMemento(Content);
        }

        public void RevertToLastSave(DocumentCaretaker prDocumentCaretaker)
        {
            Console.WriteLine($"WordDocument - Restoring state...");
            Content = prDocumentCaretaker._DocumentMemento._Content;
        }
    }

    public class DocumentMemento
    {
        public string _Content { get; set; }

        public DocumentMemento(string _prContent)
        {
            _Content = _prContent;
        }
    }

    public class DocumentCaretaker
    {
        public DocumentMemento _DocumentMemento { get; set; }
    }

Output

image.png

7. Observer

Objective ๐ŸŽฏ

Define a one-to-many dependency between objects in a way that when one object changes its state or any specific attribute, all its dependents (a.k.a. observers) will be notified automatically.

UML ๐Ÿ“

image.png

Participants ๐Ÿ”—

โ€ข Subject:

  • Holds a list of observers
  • Provides an interface for attaching/detaching Observer objects

โ€ข ConcreteSubject:

  • Sends a notification to its observers when its state/properties change

โ€ข Observer:

  • Defines an interface to allow objects to be notified when the subject changes

โ€ข ConcreteObserver:

  • Maintains a reference to a ConcreteSubject object
  • Stores an internal state that should be consistent with the Subjectโ€™s state (if needed)
  • Implements the Observer updating interface to keep its state in sync with the Subjectโ€™s

Sample Code ๐ŸŽฎ

Structural Example ๐Ÿ›๏ธ

image.png

public static class ObserverStructural
    {
        public static void Execute()
        {
            ConcreteSubject lConcreteSubject = new ConcreteSubject();
            lConcreteSubject.Attach(new ConcreteObserver1(lConcreteSubject));
            lConcreteSubject.Attach(new ConcreteObserver2(lConcreteSubject));

            lConcreteSubject.SubjectState = "State2";
            lConcreteSubject.SubjectState = "State3";
        }
    }

    public abstract class Subject
    {
        private List<Observer> _Observers = new List<Observer>();

        public void Attach(Observer prObserver)
        {
            _Observers.Add(prObserver);
        }

        public void Detach(Observer prObserver)
        {
            _Observers.Remove(prObserver);
        }

        protected void NotifyObservers()
        {
            foreach (Observer lObserverCurrent in _Observers)
            {
                lObserverCurrent.Update();
            }
        }
    }

    public abstract class Observer
    {
        public ConcreteSubject _ConcreteSubject;

        public string _Name { get; set; }
        public string _ObserverState { get; set; }

        public Observer(ConcreteSubject prConcreteSubject)
        {
            _ConcreteSubject = prConcreteSubject;
        }

        public abstract void Update();
    }

    public class ConcreteObserver1 : Observer
    {
        public ConcreteObserver1(ConcreteSubject prConcreteSubject) : base(prConcreteSubject) { }

        public override void Update()
        {
            Console.WriteLine("Concrete Observer 1 Triggered");
        }
    }

    public class ConcreteObserver2 : Observer
    {
        public ConcreteObserver2(ConcreteSubject prConcreteSubject) : base(prConcreteSubject) { }

        public override void Update()
        {
            Console.WriteLine("Concrete Observer 2 Triggered");
        }
    }

    public class ConcreteSubject : Subject
    {
        private string _SubjectState = "State1";
        public string SubjectState
        {
            get { return _SubjectState; }
            set
            {
                Console.WriteLine($"Changing Subject's State from {_SubjectState} to {value}");
                _SubjectState = value;
                NotifyObservers();
            }
        }
    }

Output

image.png

Real-world Example ๐Ÿ”ฅ

image.png

public static class ObserverPractical
    {
        public static void Execute()
        {
            JohnPoliticalFigure lJohnPoliticalFigure = new JohnPoliticalFigure();
            lJohnPoliticalFigure.Attach(new Mike());
            lJohnPoliticalFigure.Attach(new Harry());

            lJohnPoliticalFigure.NewTweet("Lorem ipsum dolor sit amet, consectetur adipiscing");
            lJohnPoliticalFigure.NewTweet("Mauris vitae orci a dolor bibendum gravida");
        }
    }

    public abstract class TwitterProfile
    {
        private List<TwitterFollower> _Followers = new List<TwitterFollower>();

        public void Attach(TwitterFollower prFollower)
        {
            _Followers.Add(prFollower);
        }

        public void Detach(TwitterFollower prFollower)
        {
            _Followers.Remove(prFollower);
        }

        protected void NotifyFollowers(string prTweet)
        {
            foreach (TwitterFollower lFollowerCurrent in _Followers)
            {
                lFollowerCurrent.NotifyNewTweet(prTweet);
            }
        }
    }

    public abstract class TwitterFollower
    {
        public abstract void NotifyNewTweet(string prTweet);
    }

    public class Mike : TwitterFollower
    {
        public override void NotifyNewTweet(string prTweet)
        {
            Console.WriteLine($"Notifying User \"Mike\"");
        }
    }

    public class Harry : TwitterFollower
    {
        public override void NotifyNewTweet(string prTweet)
        {
            Console.WriteLine($"Notifying User \"Harry\"");
        }
    }

    public class JohnPoliticalFigure : TwitterProfile
    {
        public void NewTweet(string prTweet)
        {
            Console.WriteLine("New Tweet from John: " + prTweet);
            NotifyFollowers(prTweet);
        }
    }

Output

image.png

8. State

Objective ๐ŸŽฏ

Allow an object alter its own behavior when its state changes.

UML ๐Ÿ“

image.png

Participants ๐Ÿ”—

โ€ข Context:

  • Represents the class that will be used by the Client
  • Maintains an instance of a ConcreteState class that represents the current state

โ€ข State:

  • Defines an interface for encapsulating the behavior associated with a particular state of the Context.

โ€ข Concrete State:

  • Represents a specific behavior for a particular Context

Sample Code ๐ŸŽฎ

Structural Example ๐Ÿ›๏ธ

image.png

public static class StateStructural
    {
        public static void Execute()
        {
            Context lContext = new Context(new ConcreteStateA());
            lContext.Request(); // State A > State B
            lContext.Request(); // State B > State C
            lContext.Request(); // State C > State A
        }
    }
    public abstract class State
    {
        public abstract void Handle(Context prContext);
    }

    public class ConcreteStateA : State
    {
        public override void Handle(Context prContext)
        {
            Console.WriteLine("Concrete State A - Executing method \"Handle\" and changing state to \"B\"");
            prContext._State = new ConcreteStateB();
        }
    }

    public class ConcreteStateB : State
    {
        public override void Handle(Context prContext)
        {
            Console.WriteLine("Concrete State B - Executing method \"Handle\" and changing state to \"C\"");
            prContext._State = new ConcreteStateC();
        }
    }

    public class ConcreteStateC : State
    {
        public override void Handle(Context prContext)
        {
            Console.WriteLine("Concrete State C - Executing method \"Handle\" and changing state back to \"A\"");
            prContext._State = new ConcreteStateA();
        }
    }

    public class Context
    {
        public State _State;

        public Context(State prState)
        {
            _State = prState;
        }

        public void Request()
        {
            _State.Handle(this);
        }
    }

Output

image.png

Real-world Example ๐Ÿ”ฅ

image.png

public static class StatePractical
    {
        public static void Execute()
        {
            DocumentContext lDocumentContext = new DocumentContext(new Draft());

            Console.WriteLine("\\\\ Calling method \"GoToNextStatus\" - Result: ");
            lDocumentContext.GoToNextStatus();

            Console.WriteLine("\n\r\\\\ Calling method \"GoToNextStatus\" - Result: ");
            lDocumentContext._DocumentContent = "Lorem ipsum";
            lDocumentContext.GoToNextStatus();

            Console.WriteLine("\n\r\\\\ Calling method \"GoToNextStatus\" - Result: ");
            lDocumentContext._DocumentContent = "Lorem ipsum dolor sit amet";
            lDocumentContext.GoToNextStatus();

            Console.WriteLine("\n\r\\\\ Calling method \"GoToNextStatus\" - Result: ");
            lDocumentContext.GoToNextStatus();

            Console.WriteLine("\n\r\\\\ Calling method \"GoToNextStatus\" - Result: ");
            lDocumentContext.GoToNextStatus();
        }
    }

    public abstract class DocumentState
    {
        public abstract void GoToNextStatus(DocumentContext prDocumentContext);
        public abstract void Close(DocumentContext prDocumentContext);
        public abstract void Publish(DocumentContext prDocumentContext);
    }

    public class Draft : DocumentState
    {
        public override void GoToNextStatus(DocumentContext prDocumentContext)
        {
            if (string.IsNullOrWhiteSpace(prDocumentContext._DocumentContent))
                Console.WriteLine("Is not possible to send a document with empty content for review");
            else
            {
                Console.WriteLine("Document submitted to review");
                prDocumentContext.DocumentState = new InReview();
            }
        }

        public override void Close(DocumentContext prDocumentContext)
        {
            Console.WriteLine("Is not possible to close a document in the status \"Draft\"");
        }

        public override void Publish(DocumentContext prDocumentContext)
        {
            Console.WriteLine("Is not possible to publish a document in the status \"Draft\"");
        }
    }

    public class InReview : DocumentState
    {
        public override void GoToNextStatus(DocumentContext prDocumentContext)
        {
            if (prDocumentContext._DocumentContent.Length < 15)
            {
                Console.WriteLine("Document rejected as the content has less than 15 characters");
                prDocumentContext.DocumentState = new Rejected();
            }
            else
            {
                Console.WriteLine("Review approved");
                prDocumentContext.DocumentState = new Approved();
            }
        }

        public override void Close(DocumentContext prDocumentContext)
        {
            Console.WriteLine("Document closed");
            prDocumentContext.DocumentState = new Closed();
        }

        public override void Publish(DocumentContext prDocumentContext)
        {
            Console.WriteLine("Is not possible to publish a document in the status \"In Review\"");
        }
    }

    public class Approved : DocumentState
    {
        public override void GoToNextStatus(DocumentContext prDocumentContext)
        {
            Publish(prDocumentContext);
        }

        public override void Close(DocumentContext prDocumentContext)
        {
            Console.WriteLine("Document closed");
            prDocumentContext.DocumentState = new Closed();
        }

        public override void Publish(DocumentContext prDocumentContext)
        {
            Console.WriteLine("Document published");
            prDocumentContext.DocumentState = new Published();
        }
    }

    public class Rejected : DocumentState
    {
        public override void GoToNextStatus(DocumentContext prDocumentContext)
        {
            Console.WriteLine("Document already in the last status (Rejected)");
        }

        public override void Close(DocumentContext prDocumentContext)
        {
            Console.WriteLine("Is not possible to close a rejected document");
        }

        public override void Publish(DocumentContext prDocumentContext)
        {
            Console.WriteLine("Is not possible to publish a document in the status \"Rejected\"");
        }
    }

    public class Published : DocumentState
    {
        public override void GoToNextStatus(DocumentContext prDocumentContext)
        {
            Console.WriteLine("Document already in the last status (Published)");
        }

        public override void Close(DocumentContext prDocumentContext)
        {
            Console.WriteLine("Is not possible to close a published document");
        }

        public override void Publish(DocumentContext prDocumentContext)
        {
            Console.WriteLine("Document already published");
        }
    }

    public class Closed : DocumentState
    {
        public override void GoToNextStatus(DocumentContext prDocumentContext)
        {
            Console.WriteLine("Document already in the last status (Closed)");
        }

        public override void Close(DocumentContext prDocumentContext)
        {
            Console.WriteLine("Document already closed");
        }

        public override void Publish(DocumentContext prDocumentContext)
        {
            Console.WriteLine("Is not possible to publish a closed document");
        }
    }

    public class DocumentContext
    {
        private DocumentState _DocumentState;
        public string _DocumentContent;

        public DocumentContext(DocumentState prDocumentState)
        {
            _DocumentState = prDocumentState;
        }

        public DocumentState DocumentState
        {
            get { return _DocumentState; }
            set
            {
                if (value != _DocumentState)
                {
                    Console.WriteLine($"<< Document state changed from {_DocumentState.GetType().Name} to {value.GetType().Name} >>");
                    _DocumentState = value;
                }
            }
        }

        public void GoToNextStatus() { _DocumentState.GoToNextStatus(this); }
        public void Close() { _DocumentState.Close(this); }
        public void Publish() { _DocumentState.Publish(this); }
    }

Output

image.png

9. Strategy

Objective ๐ŸŽฏ

Allow an algorithm to be changed at run time based on pre-defined conditions

UML ๐Ÿ“

image.png

Participants ๐Ÿ”—

โ€ข Strategy:

  • Declares an interface common to all supported algorithms

โ€ข ConcreteStrategy:

  • Implements the algorithm respecting the Strategy interface

โ€ข Context:

  • Maintains a reference to a concrete Strategy

Sample Code ๐ŸŽฎ

Structural Example ๐Ÿ›๏ธ

image.png

public static class StrategyStructural
    {
        public static void Execute()
        {
            Context lContext = new Context(new ConcreteStrategyA());
            lContext.ContextOperation();

            lContext = new Context(new ConcreteStrategyB());
            lContext.ContextOperation();
        }

        public abstract class Strategy
        {
            public abstract void StrategyOperation();
        }

        public class ConcreteStrategyA : Strategy
        {
            public override void StrategyOperation()
            {
                Console.WriteLine("Executing Operation Using Strategy A");
            }
        }

        public class ConcreteStrategyB : Strategy
        {
            public override void StrategyOperation()
            {
                Console.WriteLine("Executing Operation Using Strategy B");
            }
        }

        public class Context
        {
            private Strategy _Strategy;

            public Context(Strategy prStrategy)
            {
                this._Strategy = prStrategy;
            }

            public void ContextOperation()
            {
                _Strategy.StrategyOperation();
            }
        }
    }

Output

image.png

Real-world Example ๐Ÿ”ฅ

image.png

public static class StrategyPractical
    {
        public static void Execute()
        {
            SortedListContext lSortedListContext = new SortedListContext();
            lSortedListContext.AddItem("D");
            lSortedListContext.AddItem("B");
            lSortedListContext.AddItem("A");
            lSortedListContext.AddItem("C");
            lSortedListContext.AddItem("E");
            lSortedListContext.AddItem("G");
            lSortedListContext.AddItem("F");

            lSortedListContext.SetSortStrategy(new Ascending());
            lSortedListContext.Sort();

            lSortedListContext.SetSortStrategy(new Descending());
            lSortedListContext.Sort();

            lSortedListContext.SetSortStrategy(new Random());
            lSortedListContext.Sort();
        }
    }

    public abstract class SortStrategy
    {
        public abstract List<string> Sort(List<string> prItems);
    }

    public class Ascending : SortStrategy
    {
        public override List<string> Sort(List<string> prItems)
        {
            prItems = prItems.OrderBy(x => x).ToList();
            Console.WriteLine("Ascending Strategy - Result: " + String.Join(", ", prItems));
            return prItems;
        }
    }

    public class Descending : SortStrategy
    {
        public override List<string> Sort(List<string> prItems)
        {
            prItems = prItems.OrderByDescending(x => x).ToList();
            Console.WriteLine("Descending Strategy - Result: " + String.Join(", ", prItems));
            return prItems;
        }
    }

    public class Random : SortStrategy
    {
        public override List<string> Sort(List<string> prItems)
        {
            prItems = prItems.OrderBy(x => System.Guid.NewGuid().ToString()).ToList();
            Console.WriteLine("Random Strategy - Result: " + String.Join(", ", prItems));
            return prItems;
        }
    }

    public class SortedListContext
    {
        private List<string> _Items = new List<string>();
        private SortStrategy _SortStrategy;

        public void SetSortStrategy(SortStrategy prSortStrategy)
        {
            this._SortStrategy = prSortStrategy;
        }

        public void AddItem(string prItem)
        {
            _Items.Add(prItem);
        }

        public void Sort()
        {
            _SortStrategy.Sort(_Items);
        }
    }

Output

image.png

10. Template Method

Objective ๐ŸŽฏ

Defines the skeleton/structure of how an algorithm should be executed in the superclass letting the subclasses to override specific steps of the implementation without changing the main structure.

UML ๐Ÿ“

image.png

Participants ๐Ÿ”—

โ€ข AbstractClass:

  • Defines abstract primitive operations that can be overridden by subclasses
  • Defines the skeleton of the template method

โ€ข ConcreteClass:

  • Implements (overrides) the primitive operations from the AbstractClass

Sample Code ๐ŸŽฎ

Structural Example ๐Ÿ›๏ธ

image.png

public static class TemplateMethodStructural
    {
        public static void Execute()
        {
            AbstractClass lAbstractClassA = new ConcreteClassA();
            lAbstractClassA.TemplateMethod();

            AbstractClass lAbstractClassB = new ConcreteClassB();
            lAbstractClassB.TemplateMethod();
        }
    }

    public abstract class AbstractClass
    {
        public void TemplateMethod()
        {
            Step1();
            Step2();
        }
        protected abstract void Step1();
        protected abstract void Step2();
    }

    public class ConcreteClassA : AbstractClass
    {
        protected override void Step1()
        {
            Console.WriteLine("ConcreteClassA - Step1");
        }

        protected override void Step2()
        {
            Console.WriteLine("ConcreteClassA - Step2");
        }
    }

    public class ConcreteClassB : AbstractClass
    {
        protected override void Step1()
        {
            Console.WriteLine("ConcreteClassB - Step1");
        }

        protected override void Step2()
        {
            Console.WriteLine("ConcreteClassB - Step2");
        }
    }

Output

image.png

Real-world Example ๐Ÿ”ฅ

image.png

public static class TemplateMethodPractical
    {
        public static void Execute()
        {
            Person lJohn = new John();
            lJohn.DisplayDailyActivityTime();

            Person lSimon = new Simon();
            lSimon.DisplayDailyActivityTime();
        }
    }

    public abstract class Person
    {
        public void DisplayDailyActivityTime()
        {
            WakeUp();

            EatBreakFast();

            if (HasJob())
                GoToWork();

            HaveLunch();

            HaveDinner();

            GoToSleep();
        }

        protected abstract void WakeUp();
        protected abstract void EatBreakFast();
        protected abstract bool HasJob();
        protected virtual void GoToWork() { } // This action will not be mandatory as it depends if the person has job
        protected abstract void HaveLunch();
        protected abstract void HaveDinner();
        protected abstract void GoToSleep();
    }

    public class John : Person
    {
        protected override void EatBreakFast()
        {
            Console.WriteLine("John - EatBreakFast - 06:30:00");
        }

        protected override void GoToSleep()
        {
            Console.WriteLine("John - GoToSleep - 22:00:00");
        }

        protected override void GoToWork()
        {
            Console.WriteLine("John - GoToWork - 07:00:00");
        }

        protected override bool HasJob()
        {
            return true;
        }

        protected override void HaveDinner()
        {
            Console.WriteLine("John - HaveDinner - 20:00:00");
        }

        protected override void HaveLunch()
        {
            Console.WriteLine("John - HaveLunch - 12:00:00");
        }

        protected override void WakeUp()
        {
            Console.WriteLine("John - WakeUp - 06:00:00");
        }
    }

    public class Simon : Person
    {
        protected override void EatBreakFast()
        {
            Console.WriteLine("Simon - EatBreakFast - 09:00:00");
        }

        protected override void GoToSleep()
        {
            Console.WriteLine("Simon - GoToSleep - 23:00:00");
        }

        protected override bool HasJob()
        {
            return false;
        }

        protected override void HaveDinner()
        {
            Console.WriteLine("Simon - HaveDinner - 21:00:00");
        }

        protected override void HaveLunch()
        {
            Console.WriteLine("Simon - HaveLunch - 13:00:00");
        }

        protected override void WakeUp()
        {
            Console.WriteLine("Simon - WakeUp - 08:00:00");
        }
    }

Output

image.png

11. Visitor

Objective ๐ŸŽฏ

Provide a way of separating an algorithm from an object allowing to add/change operations at run time.

UML ๐Ÿ“

image.png

Participants ๐Ÿ”—

โ€ข Visitor:

  • Declares a specific method (Visit) for each ConcreteElement in the Object Structure

โ€ข ConcreteVisitor:

  • Implements each operation declared in the Visitor

โ€ข Element:

  • Defines an Accept method that receives a Visitor as an argument

โ€ข ConcreteElement:

  • Implements the Element's interface

โ€ข ObjectStructure:

  • Maintains a list of elements
  • Provides an interface to attach and remove elements in the list

Sample Code ๐ŸŽฎ

Structural Example ๐Ÿ›๏ธ

image.png

namespace Main.Visitor
{
    public static class VisitorStructural
    {
        public static void Execute()
        {
            ObjectStructure lObjectStructure = new ObjectStructure();
            lObjectStructure.Attach(new ConcreteElementA());
            lObjectStructure.Attach(new ConcreteElementB());

            ConcreteVisitor1 lConcreteVisitor1 = new ConcreteVisitor1();
            ConcreteVisitor2 lConcreteVisitor2 = new ConcreteVisitor2();

            lObjectStructure.Accept(lConcreteVisitor1);
            lObjectStructure.Accept(lConcreteVisitor2);
        }
    }

    public abstract class Visitor
    {
        public abstract void VisitConcreteElementA(ConcreteElementA prConcreteElementA);
        public abstract void VisitConcreteElementB(ConcreteElementB prConcreteElementB);
    }

    public class ConcreteVisitor1 : Visitor
    {
        public override void VisitConcreteElementA(ConcreteElementA prConcreteElementA)
        {
            Console.WriteLine("{0} visited by {1}",
                prConcreteElementA.GetType().Name, this.GetType().Name);
        }

        public override void VisitConcreteElementB(ConcreteElementB prConcreteElementB)
        {
            Console.WriteLine("{0} visited by {1}",
                prConcreteElementB.GetType().Name, this.GetType().Name);
        }
    }

    public class ConcreteVisitor2 : Visitor
    {
        public override void VisitConcreteElementA(ConcreteElementA prConcreteElementA)
        {
            Console.WriteLine("{0} visited by {1}", prConcreteElementA.GetType().Name, this.GetType().Name);
        }

        public override void VisitConcreteElementB(ConcreteElementB prConcreteElementB)
        {
            Console.WriteLine("{0} visited by {1}", prConcreteElementB.GetType().Name, this.GetType().Name);
        }
    }

    public abstract class Element
    {
        public abstract void Accept(Visitor prVisitor);
    }

    public class ConcreteElementA : Element
    {
        public override void Accept(Visitor prVisitor)
        {
            prVisitor.VisitConcreteElementA(this);
        }
    }

    public class ConcreteElementB : Element
    {
        public override void Accept(Visitor prVisitor)
        {
            prVisitor.VisitConcreteElementB(this);
        }
    }

    public class ObjectStructure
    {
        private List<Element> _Elements = new List<Element>();

        public void Attach(Element prElement)
        {
            _Elements.Add(prElement);
        }

        public void Detach(Element prElement)
        {
            _Elements.Remove(prElement);
        }

        public void Accept(Visitor prVisitor)
        {
            foreach (Element lElementCurrent in _Elements)
            {
                lElementCurrent.Accept(prVisitor);
            }
        }
    }
}

Output

image.png

Real-world Example ๐Ÿ”ฅ

image.png

public static class VisitorPractical
    {
        public static void Execute()
        {
            School lSchool = new School();
            lSchool.Attach(new Student() { _Name = "Frank" });
            lSchool.Attach(new Student() { _Name = "Matt" });
            lSchool.Attach(new Student() { _Name = "Jennifer" });

            lSchool.Accept(new Doctor());
            lSchool.Accept(new CarrerCoach());
        }
    }

    public interface IVisitor
    {
        public void Visit(Student prStudent);
    }

    public interface IElement
    {
        public void Accept(IVisitor prVisitor);
    }

    public class Student : IElement
    {
        public string _Name { get; set; }
        public void Accept(IVisitor prVisitor)
        {
            prVisitor.Visit(this);
        }
    }

    public class Doctor : IVisitor
    {
        public void Visit(Student prStudent)
        {
            Console.WriteLine($"Student \"{prStudent._Name}\" visited by {this.GetType().Name}");
        }
    }

    public class CarrerCoach : IVisitor
    {
        public void Visit(Student prStudent)
        {
            Console.WriteLine($"Student \"{prStudent._Name}\" visited by {this.GetType().Name}");
        }
    }

    public class School
    {
        private List<Student> _Students = new List<Student>();

        public void Attach(Student prStudent)
        {
            _Students.Add(prStudent);
        }

        public void Detach(Student prStudent)
        {
            _Students.Remove(prStudent);
        }

        public void Accept(IVisitor prVisitor)
        {
            foreach (Student lStudentCurrent in _Students)
            {
                lStudentCurrent.Accept(prVisitor);
            }
        }
    }

Output

image.png

Did you find this article valuable?

Support Victor Lins by becoming a sponsor. Any amount is appreciated!

ย