OOP Conundrum

 

Hi everyone -

So, my MQL4 days are slowly coming to an end. My broker has implemented MQL5 and now, the conversion from 4->5 begins. When I started MQL4, I was an OOP noob and in some respects, I still am. Building OOP classes has certainly paid huge dividends by creating a foundation of thoroughly tested and extensible objects ultimately providing substantial increases in areas of stability and performance and by limiting the number of code rewrites and duplication through polymorphism. As I venture into the MQL5 arena, I want to know that I adhere to customary design standards and standard/best practices for OOP programming; hence my conundrum.

Given the follow following class used to store session details:

//+------------------------------------------------------------------+
//|                                                      Session.mqh |
//|                                 Copyright 2018, Dennis Jorgenson |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, Dennis Jorgenson"
#property link      "http://www.mql5.com"
#property version   "1.00"
#property strict

#include <stdutil.mqh>
#include <Class/Event.mqh>

//+------------------------------------------------------------------+
//| CSession Class - Tracks session details                          |
//+------------------------------------------------------------------+
class CSession
  {

protected:

             //-- Session Types
             enum SessionType
             {
               Asia,
               Europe,
               US,
               Daily,
               SessionTypes
             };

private:

             SessionType   sSessionPeriod;
             bool          sSessionOpen;
             int           sSessionDir;
             double        sOpen;
             double        sHigh;
             double        sLow;
             double        sClose;
             int           sBoundaryDir;
             double        sBoundaryHigh;
             double        sBoundaryLow;
             bool          sBreakout;
             bool          sReversal;
             int           sReversalCount;
             CEvent       *sEvent;

public:

                     CSession(SessionType Type);
                    ~CSession(void);

             void    Open(int Bar=0);
             void    Close(int Bar=0);
             void    Update(int Bar=0);
             
             void    SetBoundary(int SessionDir, double Resistance, double Support);

             bool operator[](const EventType Type) const { return(sEvent[Type]); };

  };

//+------------------------------------------------------------------+
//| Class Constructor - instantiates the CSession class object       |
//+------------------------------------------------------------------+
CSession::CSession(SessionType Type)
  {
    sSessionPeriod   = Type;
    sSessionOpen     = false;
    sSessionDir      = DirectionNone;
    sOpen            = NoValue;
    sHigh            = NoValue;
    sLow             = NoValue;
    sClose           = NoValue;
    sBoundaryDir     = DirectionNone;
    sBoundaryHigh    = NoValue;
    sBoundaryLow     = NoValue;
    sBreakout        = false;
    sReversal        = false;
    sReversalCount   = NoValue;

    sEvent            = new CEvent();
  }

//+------------------------------------------------------------------+
//| Class Destructor - Destroys the CSession class object            |
//+------------------------------------------------------------------+
CSession::~CSession(void)
  {
    delete sEvent;
  }

//+------------------------------------------------------------------+
//| Open - Sets the session details on session open                  |
//+------------------------------------------------------------------+
CSession::Open(int Bar=0)
  {
    sSessionOpen     = true;
    sOpen            = Open[Bar];
    sHigh            = High[Bar];
    sLow             = Low[Bar];
  }

//+------------------------------------------------------------------+
//| Close - Sets the final session details on session close          |
//+------------------------------------------------------------------+
CSession::Close(int Bar=0)
  {
    sClose           = Close[Bar+1];
    sSessionOpen     = false;
  }

//+------------------------------------------------------------------+
//| Update - Updates session details on open sessions                |
//+------------------------------------------------------------------+
CSession::Update(int Bar=0)
  {
    static int usBoundaryDir = DirectionNone;
    
    sEvent.ClearEvents();
    
    if (this.sSessionOpen)
    {
      if (IsHigher(High[Bar],sHigh))
        sEvent.SetEvent(NewHigh);

      if (IsLower(Low[Bar],sLow))
        sEvent.SetEvent(NewLow);
        
      if (sEvent[NewLow] || sEvent[NewHigh])
      {
        sEvent.SetEvent(NewBoundary);
        
        if (sEvent[NewLow] && sEvent[NewHigh])
          sEvent.SetEvent(InsideReversal);
        else
        if (sEvent[NewLow])
          sSessionDir       = DirectionDown;
        else
        if (sEvent[NewHigh])
          sSessionDir       = DirectionUp;
        
        if (IsHigher(Close[Bar],sBoundaryHigh,NoUpdate))
          if (IsChanged(usBoundaryDir,DirectionUp))
            sEvent.SetEvent(NewDirection);
        
        if (IsLower(Close[Bar],sBoundaryLow,NoUpdate))
          if (IsChanged(usBoundaryDir,DirectionDown))
            sEvent.SetEvent(NewDirection);

        if (sEvent[NewDirection])
        {
          sReversalCount++;
          
          if (IsEqual(sSessionDir,sBoundaryDir))
          {
            sBreakout      = true;
            sEvent.SetEvent(NewBreakout);
          }
          else
          {
            sReversal      = true;
            sEvent.SetEvent(NewReversal);
          }
        }
      }
    }
  }

//+------------------------------------------------------------------+
//| SetBoundary - Sets the resistance/support levels of the session  |
//+------------------------------------------------------------------+
CSession::SetBoundary(int BoundaryDir, double Resistance, double Support)
  {
    sBoundaryDir      = BoundaryDir;
    
    switch (sBoundaryDir)
    {
      case DirectionUp:     sBoundaryHigh  = Resistance;
                            sBoundaryLow   = Support;
                            break;
      
      case DirectionDown:   sBoundaryHigh  = Support;
                            sBoundaryLow   = Resistance;
                            break;
    }
  }

The code is operational as from the testevent.mqh EA as follows:

//+------------------------------------------------------------------+
//|                                                    testevent.mq4 |
//|                                 Copyright 2018, Dennis Jorgenson |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "Copyright 2018, Dennis Jorgenson"
#property link      "http://www.mql5.com"
#property version   "1.00"
#property strict

#include <Class\Event.mqh>
#include <Class\Session.mqh>

   CEvent *ev_asia    = new CEvent();
   CSession *session  = new CSession(Asia);
   
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
    ev_asia.ClearEvents();
    
    return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
    delete ev_asia;   
  }
  
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
     ev_asia.SetEvent(NewLow);
     session.Update();
     
     if (ev_asia[NewHigh]) Print("1.New High");

     ev_asia.SetEvent(NewHigh);
     
     if (ev_asia[NewHigh]) Print("2.New High");

     ev_asia.ClearEvent(NewHigh);

     if (ev_asia[NewLow]) Print("3.1 New Low");
     if (ev_asia[NewHigh]) Print("3.2 New High");     
     
     ev_asia.SetEvent(NewHigh);

     if (ev_asia[NewHigh]) Print("4.New High");     

     ev_asia.ClearEvents();
     
     if (ev_asia[NewLow]) Print("5.1 New Low");
     if (ev_asia[NewHigh]) Print("5.1 New High");

    if (session[NewReversal])
      Print("Reversal");
    else
      Print("Trending");
  }


The results of the first tick show:

0       11:31:28.686    Expert testevent GBPUSD,H1: loaded successfully
0       11:31:30.716    TestGenerator: spread set to 25
2       11:31:39.606    2014.07.01 00:00:00  testevent test started
0       11:31:39.614    2014.07.01 00:00:00  testevent GBPUSD,H1: 2.New High
0       11:31:39.614    2014.07.01 00:00:00  testevent GBPUSD,H1: 3.1 New Low
0       11:31:39.614    2014.07.01 00:00:00  testevent GBPUSD,H1: 4.New High
0       11:31:39.614    2014.07.01 00:00:00  testevent GBPUSD,H1: Trending
0       11:31:39.614    2014.07.01 00:00:03  testevent GBPUSD,H1: 2.New High
0       11:31:39.614    2014.07.01 00:00:03  testevent GBPUSD,H1: 3.1 New Low
0       11:31:39.614    2014.07.01 00:00:03  testevent GBPUSD,H1: 4.New High
0       11:31:39.614    2014.07.01 00:00:03  testevent GBPUSD,H1: Trending


I love the operator overload! This represents the implementation of the CEvent class from within CSession. It provides a straightforward of "speaking", i.e., "Q: Is the session in reversal?" Code: if (session[NewReversal])... Truly, easy to understand and streamlined code. I guess the "proper" OOP approach is to implement a public method, e.g.:

public:

   ...
        bool Event(EventType Type) {return(sEvent[Type]); };
   ...


Then, access the data from the EA as follows (which, I admit, from a purist perspective is more "proper"):

   ...
    if (session.Event(NewReversal)
    {
      //do stuff
    }
   ...


But, this is not my quandary. The specifics of my venture lie in the distinction between class methods and class properties. Class data needs to be both immutable and and public. Say, for example, I ask the question, "Q: Is the session trading on its highs or trading on its lows?" the question is answered by implementing the following method:

public:

   ...
        int Direction(void) {return (sSessionDir); };
   ...


And I would then ask the question in code as:

   ...
    if (session.Direction()==DirectionUp)
    {
      //do stuff
    }
    else
    if (session.Direction()==DirectionDown)
    {
      //do stuff
    }
    else
   ...


Not bad, however, this approach seems to imply that for every immutable class property a method is required. Further noted, the '()' operator specifically describes a method 'function' implying the possibility that the value .Direction() is derived from a number of variables as opposed to the simple return of a private variable as presented. Using this approach, the code requires a substantial number of class property methods as follows:

public:

   ...
        SessionType Type(void)      {return (sSessionType); };
        bool        IsOpen(void)    {return (sSessionOpen); };
        int         Direction(void) {return (sSessionDir; };
        double      Open(void)      {return (sOpen); };
        double      High(void)      {return (sHigh); };
        double      Low(void)       {return (sLow); };
        double      Close(void)     {return (sClose); };
   ...


Resulting in code that looks that likes this:

  ...
    SessionType st  = session.Type();
    bool        sih = session.IsOpen();
    int         d   = session.Direction();
    double      so  = session.Open();
    double      sh  = session.High();
    double      sl  = session.Low();
    double      sc  = session.Close();
  ...

OR
    if (session.High()>=High[0])
    {
      // do stuff
    }


So, my quandary is: Is there a coding practice, standard, or approach to expose immutable (private/protected) class properties simply using '.' notation without having to define a method for each property? Given that I completely buy into the fact that the class data only be modified by class methods, I'm hoping I missed something in terms of exposing class properties that permits notation along the lines of:

  ...
    SessionType st  = session.sSessionType;
    bool        sih = session.sSessionOpen;
    int         d   = session.sSessionDir;
    double      so  = session.sOpen;
    double      sh  = session.sHigh;
    double      sl  = session.sLow;
    double      sc  = session.sClose;
  ...

OR
    if (session.sHigh>=High[0])
    {
      // do stuff
    }

OR better, given that the identity of a class property can be aliased and the '()' notation can be dropped as follows:

    if (session.High>=High[0])
    {
      // do stuff
    }
 
dennisj2: Is there a coding practice, standard, or approach to expose immutable (private/protected) class properties simply using '.' notation without having to define a method for each property?

No

 
Thanks