Design Patterns in software development and MQL5 (Part 3): Behavioral Patterns 1

Mohamed Abdelmaaboud | 5 December, 2023

Introduction

In this article, we will continue our series about the Design Patterns topic in the software domain. We identified two types of these patterns which are the Creational and Structural patterns in the previous two articles in this series and in this article, we will identify the Behavioral Design patterns which is the third type after identifying and understanding what behavioral patterns and how they can be useful when creating, building, or developing our software.  After that, we will learn how we can use them in the MQL5 to create our software for MetaTrader 5 to create a reliable, maintainable, reusable, well-tested, and extendable software.

The following topics are about what we will mention to cover this important type of pattern:

If this is the first reading article in this series, I hope that you read other articles about Creational and Structural patterns to take an overall view of one of the most important topics in software development which is Design Patterns because it can be very useful in your way to create your software.

Disclaimer: All information provided 'as is' only for educational purposes and is not prepared for trading purposes or advice. The information does not guarantee any kind of result. If you choose to use these materials on any of your trading accounts, you will do that at your own risk and you will be the only responsible.

Behavioral patterns

In the context of talking about Design Patterns and after talking about Creational and Structural Patterns, we will talk about the last type of these design patterns which are behavioral Patterns. We learned that creational patterns are these patterns that help to create an independent software or system by creating, composing, and representing objects. In addition to that learning that structural patterns are these patterns that can be used to build larger structures by using created objects and classes.

In this article, we will provide the behavioral patterns that are concerned with assigning and setting how are responsibilities between objects. They also identify how objects can communicate or interact with each other and there are many patterns under this type, the same as the following:

Because there are many patterns that can not be covered in one article, our focus in this article will be on the first five patterns only the same as the following:

If you read the previous two articles about Design Patterns, you will be familiar with the approach that we will use to cover every pattern and it will be the same as the following:


Chain of responsibility

In this part, we will understand what is the Chain of responsibility by learning what it can do, and solve, and how we can use it in the MQL5. When we need to handle a request from the client, in case we have many objects that can handle the requests of client based on the responsibility of everyone, we can use this pattern to handle this case.

Despite the advantages of using this pattern, there are pitfalls that we can face such as the following:

What does the pattern do?

This pattern can be useful in decoupling the sender of any request and the receiver of that request by giving the opportunity of many objects to handle the request. This happens through chaining the receiving objects and passing the request to everyone to check which one can handle the request.

The following is the graph for the structure of the pattern:

CHAIN OF RESPONSIBILITY

As we can see in the previous graph, we have the following participants:

What does design pattern solve?

This pattern can be used if the following is applicable:

So, this pattern can solve the following:

How can we use it in MQL5?

In this part, we will learn how to use this pattern in MQL5 to create effective MetaTrader5 software, so, the following are steps to code the Chain of Responsibility in the MQL5:

Declaring the Chain_Of_Responsibility area to include functions and variables of the pattern within by using the namespace keyword

namespace Chain_Of_Responsibility

Declare the participant of the Handler class that handles requests from the client and may implement the successor link

class Handler
  {
public:
   Handler*          successor;
   virtual void      HandleRequest(int)=0;
                    ~Handler(void);
  };
Handler::~Handler(void)
  {
   delete successor;
  }

Declare the participant of the ConcreteHandler1 class that handles requests that it is responsible for or pass the request to its successor if can handle it

class ConcreteHandler1:public Handler
  {
public:
   void              HandleRequest(int);
  };
void ConcreteHandler1::HandleRequest(int request)
  {
   if(request==1)
      Print("The request: ",request,". The request handled by: ",&this);
   else
      if(CheckPointer(successor))
        {
         Print("The request: ",request,". The request cannot be handled by ",&this,", but it is forwarding to the successor...");
         successor.HandleRequest(request);
        }
  }

Declare the ConcreteHandler2 class as a participant also

class ConcreteHandler2:public Handler
  {
public:
   void              HandleRequest(int);
  };
void ConcreteHandler2::HandleRequest(int request)
  {
   if(request==2)
      Print("The request: ",request,". The request handled by: ",&this);
   else
      if(CheckPointer(successor))
        {
         Print("The request: ",request,". The request cannot be handled by ",&this,", forwarding to successor...");
         successor.HandleRequest(request);
        }
  }

Declare the client class that initiates the request to the concrete handler in the chain

class Client
  {
public:
   string            Output();
   void              Run();
  };
string Client::Output()
  {
   return __FUNCTION__;
  }

The function of running the client is to send the request to the chain to be handled or passed to the successor

void   Client::Run()
  {
   Handler* h1=new ConcreteHandler1();
   Handler* h2=new ConcreteHandler2();
   h1.successor=h2;
   h1.HandleRequest(1);
   h1.HandleRequest(2);
   delete h1;
  }

So, the following is the full code to use the Chain of Responsibility pattern in the MQL5 in one block

//+------------------------------------------------------------------+
//|                                      Chain_Of_Responsibility.mqh |
//+------------------------------------------------------------------+
namespace Chain_Of_Responsibility
{
class Handler
  {
public:
   Handler*          successor;
   virtual void      HandleRequest(int)=0;
                    ~Handler(void);
  };
Handler::~Handler(void)
  {
   delete successor;
  }
class ConcreteHandler1:public Handler
  {
public:
   void              HandleRequest(int);
  };
void ConcreteHandler1::HandleRequest(int request)
  {
   if(request==1)
      Print("The request: ",request,". The request handled by: ",&this);
   else
      if(CheckPointer(successor))
        {
         Print("The request: ",request,". The request cannot be handled by ",&this,", but it is forwarding to the successor...");
         successor.HandleRequest(request);
        }
  }
class ConcreteHandler2:public Handler
  {
public:
   void              HandleRequest(int);
  };
void ConcreteHandler2::HandleRequest(int request)
  {
   if(request==2)
      Print("The request: ",request,". The request handled by: ",&this);
   else
      if(CheckPointer(successor))
        {
         Print("The request: ",request,". The request cannot be handled by ",&this,", but it is forwarding to successor...");
         successor.HandleRequest(request);
        }
  }
class Client
  {
public:
   string            Output();
   void              Run();
  };
string Client::Output()
  {
   return __FUNCTION__;
  }
void   Client::Run()
  {
   Handler* h1=new ConcreteHandler1();
   Handler* h2=new ConcreteHandler2();
   h1.successor=h2;
   h1.HandleRequest(1);
   h1.HandleRequest(2);
   delete h1;
  }
}


Command

In this part, we will identify another Behavioral pattern which is the Command pattern also known as Action and Transaction. This Pattern helps to encapsulate the request in an object and this allows us to set our parameters for different requests without changing the sender or receiver which means that now there is a decoupling applied between the sender, the processor, and the receiver. This is very helpful when we have huge functionalities within classes. This pattern also supports undo operations.

The same as most things there are some pitfalls when using this pattern and they are the same as the following:

What does the pattern do?

Simply, It creates an encapsulated invoker to receive the command and send it to the receiver.

The following is for the graph of the Command pattern:

COMMAND

As we can see in the previous graph of the structure of the Command pattern we have the following as participants:

What does design pattern solve?

How can we use it in MQL5?

In this part, we will take a look at a method that can be used to use this Command pattern in the MQL5 and the following steps are for doing that:

Declaring our Command space for specifying our functions, variables, classes ... etc by using the namespace keyword

namespace Command

Declaring the Receiver class as a participant which identifies the method to perform operations of the request

class Receiver
  {
public:
                     Receiver(void);
                     Receiver(Receiver&);
   void              Action(void);
  };
Receiver::Receiver(void)
  {
  }
Receiver::Receiver(Receiver &src)
  {
  }
void Receiver::Action(void)
  {
  }

Declaring the Command class as a participant to declare the operation interface

class Command
  {
protected:
   Receiver*         m_receiver;
public:
                     Command(Receiver*);
                    ~Command(void);
   virtual void      Execute(void)=0;
  };
Command::Command(Receiver* receiver)
  {
   m_receiver=new Receiver(receiver);
  }
Command::~Command(void)
  {
   if(CheckPointer(m_receiver)==1)
     {
      delete m_receiver;
     }
  }

Declaring the ConcreteCommand class as a participant to create the link between the receiver and the action or the command and implement execute() after calling the receiver operation

class ConcreteCommand:public Command
  {
protected:
   int               m_state;
public:
                     ConcreteCommand(Receiver*);
   void              Execute(void);
  };
ConcreteCommand::ConcreteCommand(Receiver* receiver):
   Command(receiver),
   m_state(0)
  {
  }
void ConcreteCommand::Execute(void)
  {
   m_receiver.Action();
   m_state=1;
  }

Declaring the Invoker class as a participant to receive the command to execute the request

class Invoker
  {
public:
                    ~Invoker(void);
   void              StoreCommand(Command*);
   void              Execute(void);
protected:
   Command*          m_command;
  };
Invoker::~Invoker(void)
  {
   if(CheckPointer(m_command)==1)
     {
      delete m_command;
     }
  }
void Invoker::StoreCommand(Command* command)
  {
   m_command=command;
  }
void Invoker::Execute(void)
  {
   m_command.Execute();
  }

Declaring the client class as a participant to create the concrete command set its receiver and run it

class Client
  {
public:
   string            Output(void);
   void              Run(void);
  };
string Client::Output(void)
  {
   return __FUNCTION__;
  }
void Client::Run(void)
  {
   Receiver receiver;
   Invoker invoker;
   invoker.StoreCommand(new ConcreteCommand(&receiver));
   invoker.Execute();
  }

So, we can find the following code is for the full code in one block of code

//+------------------------------------------------------------------+
//|                                                      Command.mqh |
//+------------------------------------------------------------------+
namespace Command
{
class Receiver
  {
public:
                     Receiver(void);
                     Receiver(Receiver&);
   void              Action(void);
  };
Receiver::Receiver(void)
  {
  }
Receiver::Receiver(Receiver &src)
  {
  }
void Receiver::Action(void)
  {
  }
class Command
  {
protected:
   Receiver*         m_receiver;
public:
                     Command(Receiver*);
                    ~Command(void);
   virtual void      Execute(void)=0;
  };
Command::Command(Receiver* receiver)
  {
   m_receiver=new Receiver(receiver);
  }
Command::~Command(void)
  {
   if(CheckPointer(m_receiver)==1)
     {
      delete m_receiver;
     }
  }
class ConcreteCommand:public Command
  {
protected:
   int               m_state;
public:
                     ConcreteCommand(Receiver*);
   void              Execute(void);
  };
ConcreteCommand::ConcreteCommand(Receiver* receiver):
   Command(receiver),
   m_state(0)
  {
  }
void ConcreteCommand::Execute(void)
  {
   m_receiver.Action();
   m_state=1;
  }
class Invoker
  {
public:
                    ~Invoker(void);
   void              StoreCommand(Command*);
   void              Execute(void);
protected:
   Command*          m_command;
  };
Invoker::~Invoker(void)
  {
   if(CheckPointer(m_command)==1)
     {
      delete m_command;
     }
  }
void Invoker::StoreCommand(Command* command)
  {
   m_command=command;
  }
void Invoker::Execute(void)
  {
   m_command.Execute();
  }
class Client
  {
public:
   string            Output(void);
   void              Run(void);
  };
string Client::Output(void)
  {
   return __FUNCTION__;
  }
void Client::Run(void)
  {
   Receiver receiver;
   Invoker invoker;
   invoker.StoreCommand(new ConcreteCommand(&receiver));
   invoker.Execute();
  }
}


Interpreter

Another Behavioral pattern that can be used to help us to set interaction between objects through given a language and define the representation for rules or grammar with an interpreter that can use this representation later to explain and interpret the content of this language.

There are pitfalls when using this type of pattern the same as the following:

What does the pattern do?

This pattern helps us describe how to define the grammar of a language, represent the content of this language, and get an interpretation of this content.

If we need to see the graph of the interpreter pattern, it will be the same as the following:

Interpreter

As we can see in the previous graph we have the following participants in this pattern:

What does design pattern solve?

As we identified, this pattern can be used when we have a language that we need to interpret and we can define or represent content in the language.

So, the following are the best cases that we can use the pattern for:

So, by using this pattern we can get the following benefits:

How can we use it in MQL5?

In this part of the article, we will present a simple method to code or use this type of pattern. The following are steps to use this Interpreter in the MQL5:

Declare the area of Interpreter that we will use to define and declare our functions, variables, and classes the same as we know by using the namespace keyword

namespace Interpreter

Declare the context class as a participant

class Context
  {
public:
   string            m_source;
   char              m_vocabulary;
   int               m_position;
   bool              m_result;
   //---
                     Context(char,string);
   void              Result(void);
  };
Context::Context(char vocabulary,string source):
   m_source(source),
   m_vocabulary(vocabulary),
   m_position(0),
   m_result(false)
  {
  }
void Context::Result(void)
  {
  }

Declare the Abstract class as a participant

class AbstractExpression
  {
public:
   virtual void      Interpret(Context&)=0;
  };

Declare the TerminalExpression class as a participant to implement the interpret method

class TerminalExpression:public AbstractExpression
  {
public:
   void              Interpret(Context&);
  };
void TerminalExpression::Interpret(Context& context)
  {
   context.m_result=
      StringSubstr(context.m_source,context.m_position,1)==
      CharToString(context.m_vocabulary);
  }

Declare the NonterminalExpression class as a participant

class NonterminalExpression:public AbstractExpression
  {
protected:
   AbstractExpression* m_nonterminal_expression;
   AbstractExpression* m_terminal_expression;
public:
   void              Interpret(Context&);
                    ~NonterminalExpression(void);
  };
NonterminalExpression::~NonterminalExpression(void)
  {
   delete m_nonterminal_expression;
   delete m_terminal_expression;
  }
void NonterminalExpression::Interpret(Context& context)
  {
   if(context.m_position<StringLen(context.m_source))
     {
      m_terminal_expression=new TerminalExpression;
      m_terminal_expression.Interpret(context);
      context.m_position++;
      if(context.m_result)
        {
         m_nonterminal_expression=new NonterminalExpression;
         m_nonterminal_expression.Interpret(context);
        }
     }
  }

Declare the client as a participant

class Client
  {
public:
   string            Output(void);
   void              Run(void);
  };
string Client::Output(void) {return __FUNCTION__;}
void Client::Run(void)
  {
   Context context_1('a',"aaa");
   Context context_2('a',"aba");
   AbstractExpression* expression;
   expression=new NonterminalExpression;
   expression.Interpret(context_1);
   context_1.Result();
   delete expression;
   expression=new NonterminalExpression;
   expression.Interpret(context_2);
   context_2.Result();
   delete expression;
  }

So, the following is the full code to use the interpreter pattern in one block of code

//+------------------------------------------------------------------+
//|                                                  Interpreter.mqh |
//+------------------------------------------------------------------+
namespace Interpreter
{
class Context
  {
public:
   string            m_source;
   char              m_vocabulary;
   int               m_position;
   bool              m_result;
                     Context(char,string);
   void              Result(void);
  };
Context::Context(char vocabulary,string source):
   m_source(source),
   m_vocabulary(vocabulary),
   m_position(0),
   m_result(false)
  {
  }
void Context::Result(void)
  {
  }
class AbstractExpression
  {
public:
   virtual void      Interpret(Context&)=0;
  };
class TerminalExpression:public AbstractExpression
  {
public:
   void              Interpret(Context&);
  };
void TerminalExpression::Interpret(Context& context)
  {
   context.m_result=
      StringSubstr(context.m_source,context.m_position,1)==
      CharToString(context.m_vocabulary);
  }
class NonterminalExpression:public AbstractExpression
  {
protected:
   AbstractExpression* m_nonterminal_expression;
   AbstractExpression* m_terminal_expression;
public:
   void              Interpret(Context&);
                    ~NonterminalExpression(void);
  };
NonterminalExpression::~NonterminalExpression(void)
  {
   delete m_nonterminal_expression;
   delete m_terminal_expression;
  }
void NonterminalExpression::Interpret(Context& context)
  {
   if(context.m_position<StringLen(context.m_source))
     {
      m_terminal_expression=new TerminalExpression;
      m_terminal_expression.Interpret(context);
      context.m_position++;
      if(context.m_result)
        {
         m_nonterminal_expression=new NonterminalExpression;
         m_nonterminal_expression.Interpret(context);
        }
     }
  }
class Client
  {
public:
   string            Output(void);
   void              Run(void);
  };
string Client::Output(void) {return __FUNCTION__;}
void Client::Run(void)
  {
   Context context_1('a',"aaa");
   Context context_2('a',"aba");
   AbstractExpression* expression;
   expression=new NonterminalExpression;
   expression.Interpret(context_1);
   context_1.Result();
   delete expression;
   expression=new NonterminalExpression;
   expression.Interpret(context_2);
   context_2.Result();
   delete expression;
  }
}


Iterator

We will identify the Iterator design pattern which is one of the behavioral patterns to set the method of interaction or communication between objects. This pattern helps that an aggregate object such as a list is present or gives us a method to access elements in a sequenced way without exposing its underlying details of representation or internal structure. It is also known as Cursor.

Despite the benefits we can get when using this Iterator pattern there are pitfalls the same as below:

What does the pattern do?

This pattern can support variations in case of complex aggregates that may be traversed in different ways because it is easy to update the traversal algorithm by replacing the instance of the iterator in addition to defining subclasses for the iterator to support the updated traversals. It makes the interface of the aggregate simple. Keeping track of the traversal state of the iterator allows that we can have many traversals processed at one time.

We can find the graph of this pattern the same as the following graph:

ITERATOR

Based on the previous graph of the Iterator structure we can find the following participants:

What does design pattern solve?

This iterator pattern can be used when we have the following applicable:

How can we use it in MQL5?

In this part, we will learn how we can use this pattern in the MQL5 through the following steps:

Defining ERRITERAOR-UT-OF-BOUNDS by using the preprocessor #define

#define ERR_ITERATOR_OUT_OF_BOUNDS 1

We will use the template keyword and declare T as a CurrentItem in the defined Iterator interface

template<typename T>
interface Iterator
  {
   void     First(void);
   void     Next(void);
   bool     IsDone(void);
   T        CurrentItem(void);
  };

Also, we will use the template keyword and declare T as an operator in the defined Aggregate interface

template<typename T>
interface Aggregate
  {
   Iterator<T>*   CreateIterator(void);
   int            Count(void);
   T              operator[](int at);
   void           operator+=(T item);
  };

Implementing the iterator interface and keeping track of the aggregate traversal as a current position after declaring the ConcreteIterator class

template<typename T>
class ConcreteIterator:public Iterator<T>
  {
public:
   void              First(void);
   void              Next(void);
   bool              IsDone(void);
   T                 CurrentItem(void);
                     ConcreteIterator(Aggregate<T>&);
protected:
   Aggregate<T>*     m_aggregate;
   int               m_current;
  };
template<typename T> 
   ConcreteIterator::ConcreteIterator(Aggregate<T>& aggregate):
   m_aggregate(&aggregate),
   m_current(0)
  {
  }
template<typename T>
void ConcreteIterator::First(void)
  {
   m_current=0;
  }
template<typename T>
void ConcreteIterator::Next(void)
  {
   m_current++;
   if(!IsDone())
     {
     }
  }
template<typename T>
bool ConcreteIterator::IsDone(void)
  {
   return m_current>=m_aggregate.Count();
  }
template<typename T>
string ConcreteIterator::CurrentItem(void)
  {
   if(IsDone())
     {
      SetUserError(ERR_ITERATOR_OUT_OF_BOUNDS);
      return NULL;
     }
   return m_aggregate[m_current];
  }

Implementing the iterator creation interface to get the instance of the suitable concrete iterator as a return value

class ConcreteAggregate:public Aggregate<string>
  {
public:
   Iterator<string>* CreateIterator(void);
   int               Count(void);
   void              operator+=(string item);
   string            operator[](int at);
protected:
   string            m_items[];
  };
Iterator<string>* ConcreteAggregate::CreateIterator(void)
  {
   return new ConcreteIterator<string>(this);
  }
void ConcreteAggregate::operator+=(string item)
  {
   int size=ArraySize(m_items);
   ArrayResize(m_items,size+1);
   m_items[size]=item;
  }
string ConcreteAggregate::operator[](int at)
  {
   return m_items[at];
  }
int ConcreteAggregate::Count()
  {
   return ArraySize(m_items);
  }

So, the following is the full code in one block of code to use the iterator pattern in the MQL5

//+------------------------------------------------------------------+
//|                                                201021_104101.mqh |
//+------------------------------------------------------------------+
#define ERR_ITERATOR_OUT_OF_BOUNDS 1
template<typename T>
interface Iterator
  {
   void     First(void);
   void     Next(void);
   bool     IsDone(void);
   T        CurrentItem(void);
  };
template<typename T>
interface Aggregate
  {
   Iterator<T>*   CreateIterator(void);
   int            Count(void);
   T              operator[](int at);
   void           operator+=(T item);
  };

template<typename T>
class ConcreteIterator:public Iterator<T>
  {
public:
   void              First(void);
   void              Next(void);
   bool              IsDone(void);
   T                 CurrentItem(void);
                     ConcreteIterator(Aggregate<T>&);
protected:
   Aggregate<T>*     m_aggregate;
   int               m_current;
  };
template<typename T> 
   ConcreteIterator::ConcreteIterator(Aggregate<T>& aggregate):
   m_aggregate(&aggregate),
   m_current(0)
  {
  }
template<typename T>
void ConcreteIterator::First(void)
  {
   m_current=0;
  }
template<typename T>
void ConcreteIterator::Next(void)
  {
   m_current++;
   if(!IsDone())
     {
     }
  }
template<typename T>
bool ConcreteIterator::IsDone(void)
  {
   return m_current>=m_aggregate.Count();
  }
template<typename T>
string ConcreteIterator::CurrentItem(void)
  {
   if(IsDone())
     {
      SetUserError(ERR_ITERATOR_OUT_OF_BOUNDS);
      return NULL;
     }
   return m_aggregate[m_current];
  }
class ConcreteAggregate:public Aggregate<string>
  {
public:
   Iterator<string>* CreateIterator(void);
   int               Count(void);
   void              operator+=(string item);
   string            operator[](int at);
protected:
   string            m_items[];
  };
Iterator<string>* ConcreteAggregate::CreateIterator(void)
  {
   return new ConcreteIterator<string>(this);
  }
void ConcreteAggregate::operator+=(string item)
  {
   int size=ArraySize(m_items);
   ArrayResize(m_items,size+1);
   m_items[size]=item;
  }
string ConcreteAggregate::operator[](int at)
  {
   return m_items[at];
  }
int ConcreteAggregate::Count()
  {
   return ArraySize(m_items);
  }


Mediator

Another Behavioral design pattern that can be used in setting how objects can interact with each other. This pattern is the Mediator pattern which can be used when we have a set of objects and we need to define an encapsulated object to describe how this set of objects can interact. It allows or applies decoupling also which can be useful and let us vary the interaction of objects independently.

The same as anything that can have benefits and pitfalls, here are the following pitfalls for the Mediator design pattern:

What does the pattern do?

As per what we mentioned as an identification for the Mediator pattern we can say that it helps to set the interaction method between objects without mentioning each object in an explicit way. So, it helps apply decoupling between objects. It also may be used as a router and it is used for communication management.

The following is a graph for the Mediator pattern to see its structure:

Mediator

Mediator(2)

As we can see in the previous graph of the structure of the pattern we have the following participants for the Mediator pattern:

What does design pattern solve?

Throughout what we understood till now this pattern can be used to solve or when we have the following:

So, we can say that:

How can we use it in MQL5?

If we need to know how we can use this pattern in the MQL5 we can do that through the following steps:

Using the interface keyword to create the Colleague interface

interface Colleague
  {
   void Send(string message);
  };
Using the interface keyword to create the Mediator interface
interface Mediator
  {
   void Send(string message,Colleague& colleague);
  };

Declare the ConcreteColleague1 class

class ConcreteColleague1:public Colleague
  {
protected:
   Mediator*         m_mediator;
public:
                     ConcreteColleague1(Mediator& mediator);
   void              Notify(string message);
   void              Send(string message);
  };
ConcreteColleague1::ConcreteColleague1(Mediator& meditor):
   m_mediator(&meditor)
  {
  }
void ConcreteColleague1::Notify(string message)
  {
  }
void ConcreteColleague1::Send(string message)
  {
   m_mediator.Send(message,this);
  }

Declare the ConcreteColleague2 class

class ConcreteColleague2:public Colleague
  {
protected:
   Mediator*         m_mediator;
public:
                     ConcreteColleague2(Mediator& mediator);
   void              Notify(string message);
   void              Send(string message);
  };
ConcreteColleague2::ConcreteColleague2(Mediator& mediator):
   m_mediator(&mediator)
  {
  }
void ConcreteColleague2::Notify(string message)
  {
  }
void ConcreteColleague2::Send(string message)
  {
   m_mediator.Send(message,this);
  }

Declare the ConcreteMediator class

class ConcreteMediator:public Mediator
  {
public:
   ConcreteColleague1*  colleague_1;
   ConcreteColleague2*  colleague_2;
   void              Send(string message,Colleague& colleague);
  };
void ConcreteMediator::Send(string message,Colleague& colleague)
  {
   if(colleague_1==&colleague)
      colleague_2.Notify(message);
   else
      colleague_1.Notify(message);
  }

So, we can find the full code to use the Mediator design pattern in the MQL5 in one block of code the same as the following

//+------------------------------------------------------------------+
//|                                                     Mediator.mqh |
//+------------------------------------------------------------------+
interface Colleague
  {
   void Send(string message);
  };
interface Mediator
  {
   void Send(string message,Colleague& colleague);
  };
class ConcreteColleague1:public Colleague
  {
protected:
   Mediator*         m_mediator;
public:
                     ConcreteColleague1(Mediator& mediator);
   void              Notify(string message);
   void              Send(string message);
  };
ConcreteColleague1::ConcreteColleague1(Mediator& meditor):
   m_mediator(&meditor)
  {
  }
void ConcreteColleague1::Notify(string message)
  {
  }
void ConcreteColleague1::Send(string message)
  {
   m_mediator.Send(message,this);
  }
class ConcreteColleague2:public Colleague
  {
protected:
   Mediator*         m_mediator;
public:
                     ConcreteColleague2(Mediator& mediator);
   void              Notify(string message);
   void              Send(string message);
  };
ConcreteColleague2::ConcreteColleague2(Mediator& mediator):
   m_mediator(&mediator)
  {
  }
void ConcreteColleague2::Notify(string message)
  {
  }
void ConcreteColleague2::Send(string message)
  {
   m_mediator.Send(message,this);
  }
class ConcreteMediator:public Mediator
  {
public:
   ConcreteColleague1*  colleague_1;
   ConcreteColleague2*  colleague_2;
   void              Send(string message,Colleague& colleague);
  };
void ConcreteMediator::Send(string message,Colleague& colleague)
  {
   if(colleague_1==&colleague)
      colleague_2.Notify(message);
   else
      colleague_1.Notify(message);
  }


Conclusion

Now, it is supposed that you got information about the third type of design patterns which is one of the most important topics in programming and software development. We mentioned in this article, some behavioral design patterns and identifying what they are and how they can be useful to create reusable, extended, maintainable, and tested software throughout learning what every pattern can do, problems or issues that can solved by using each pattern, benefits and pitfalls of each patterns, and how we can use each pattern in the MQL5 to create effective trading systems for the MetaTrader 5.

We mentioned the following patterns from the behavioral design patterns:

If this is the first article that you read for me about design patterns, I recommend to read my other articles about Design Patterns in software development and MQL5 (Part I): Creational Patterns and Design Patterns in software development and MQL5 (Part 2): Structural Patterns if you need to learn more about other types of design patterns and I hope that you find them useful.

I recommend also reading more about the design patterns topic as it will help you to create effective software the following are some useful resources about that topic:

If you want to read more articles about creating trading systems for the MetaTrader 5 using the most popular technical indicators you can check my other articles about that through my publication page and I hope that you find them useful for your trading to get useful insights and enhance your results or develop your background as a developer to improve projects that you work on.