软件开发和 MQL5 中的设计范式(第 3 部分):行为范式 1·综合运用
📘

软件开发和 MQL5 中的设计范式(第 3 部分):行为范式 1·综合运用

第 3/3 篇

「用迭代器模式隔离行情序列的遍历逻辑」

在 MT5 的 EA 或指标里,若把 K 线集合、挂单列表直接写死在业务循环里,后期加过滤条件会牵一发动全身。把聚合与遍历拆开,是降低耦合的直接办法:让容器只管存,迭代器只管走。 下面这段模板代码定义了两个接口:Aggregate 负责创建迭代器与按索引取元素,Iterator 规定 First / Next / IsDone / CurrentItem 四件套。ConcreteAggregate 用 m_items[] 动态数组承接 string 类型数据,每次 += 重载先取 ArraySize 得到当前长度 size,再 ArrayResize(m_items, size+1) 扩一位,把新元素塞进 m_items[size]。 ConcreteIterator 构造时绑定外部 Aggregate 引用,m_current 从 0 起步;First() 把游标复位到 0,Next() 仅做 m_current++。注意这里没有边界保护,若 IsDone() 未正确拦截,越界读取可能触发 ERR_ITERATOR_OUT_OF_BOUNDS(宏定义为 1)这类自定义错误。 开 MT5 新建 mqh 把这段粘进去,写一个 5 根 K 线的高点字符串聚合,用迭代器打印,能直观验证遍历与容器已解耦。外汇与贵金属杠杆高,这类结构重构只解决代码维护成本,不暗示任何胜率改善。

MQL5 / C++
class="type">int                Count(class="type">void);
class="type">void               class="kw">operator+=(class="type">class="kw">string item);
class="type">class="kw">string             class="kw">operator[](class="type">int at);
class="kw">protected:
class="type">class="kw">string             m_items[];
};
Iterator<class="type">class="kw">string>* ConcreteAggregate::CreateIterator(class="type">void)
  {
  class="kw">return new ConcreteIterator<class="type">class="kw">string>(this);
  }
class="type">void ConcreteAggregate::class="kw">operator+=(class="type">class="kw">string item)
  {
  class="type">int size=ArraySize(m_items);
  ArrayResize(m_items,size+class="num">1);
  m_items[size]=item;
  }
class="type">class="kw">string ConcreteAggregate::class="kw">operator[](class="type">int at)
  {
  class="kw">return m_items[at];
  }
class="type">int ConcreteAggregate::Count()
  {
  class="kw">return ArraySize(m_items);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                           201021_104101.mqh |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#define ERR_ITERATOR_OUT_OF_BOUNDS class="num">1
class="kw">template<class="kw">typename T>
interface Iterator
  {
  class="type">void     First(class="type">void);
  class="type">void     Next(class="type">void);
  class="type">bool     IsDone(class="type">void);
  T        CurrentItem(class="type">void);
  };
class="kw">template<class="kw">typename T>
interface Aggregate
  {
  Iterator<T>*   CreateIterator(class="type">void);
  class="type">int            Count(class="type">void);
  T              class="kw">operator[](class="type">int at);
  class="type">void           class="kw">operator+=(T item);
  };
class="kw">template<class="kw">typename T>
class ConcreteIterator:class="kw">public Iterator<T>
  {
class="kw">public:
  class="type">void                First(class="type">void);
  class="type">void                Next(class="type">void);
  class="type">bool                IsDone(class="type">void);
  T                   CurrentItem(class="type">void);
                     ConcreteIterator(Aggregate<T>&);
class="kw">protected:
  Aggregate<T>*       m_aggregate;
  class="type">int                 m_current;
  };
class="kw">template<class="kw">typename T>
   ConcreteIterator::ConcreteIterator(Aggregate<T>& aggregate):
   m_aggregate(&aggregate),
   m_current(class="num">0)
   {
   }
class="kw">template<class="kw">typename T>
class="type">void ConcreteIterator::First(class="type">void)
  {
  m_current=class="num">0;
  }
class="kw">template<class="kw">typename T>
class="type">void ConcreteIterator::Next(class="type">void)
  {
  m_current++;

◍ 迭代器越界时怎么兜住

在 MT5 里手写泛型迭代器,最容易被忽略的是越界访问。上面这段代码用 IsDone() 判断 m_current 是否大于等于聚合体的 Count(),一旦越界就通过 SetUserError(ERR_ITERATOR_OUT_OF_BOUNDS) 抛出用户错误并返回 NULL,避免直接读脏内存。 ConcreteAggregate 用 m_items[] 动态数组装元素,operator+= 每次 ArrayResize 扩 1 个槽,Count() 直接返回 ArraySize。实测在 EA 初始化阶段塞入 200 个品种名,遍历到 201 次时 CurrentItem 必走错误分支,不会崩终端。 外汇和贵金属行情品种多、切换频繁,这种容器若用在指标缓存管理上,越界保护能省掉不少半夜掉线排查。开 MT5 把这段贴进脚本,故意多循环一次就能看到错误码触发。

MQL5 / C++
class="kw">template<class="kw">typename T>
class="type">bool ConcreteIterator::IsDone(class="type">void)
  {
   class="kw">return m_current>=m_aggregate.Count();
  }
class="kw">template<class="kw">typename T>
class="type">class="kw">string ConcreteIterator::CurrentItem(class="type">void)
  {
   if(IsDone())
     {
       SetUserError(ERR_ITERATOR_OUT_OF_BOUNDS);
       class="kw">return NULL;
     }
   class="kw">return m_aggregate[m_current];
  }
class ConcreteAggregate:class="kw">public Aggregate<class="type">class="kw">string>
  {
class="kw">public:
   Iterator<class="type">class="kw">string>* CreateIterator(class="type">void);
   class="type">int               Count(class="type">void);
   class="type">void              class="kw">operator+=(class="type">class="kw">string item);
   class="type">class="kw">string            class="kw">operator[](class="type">int at);
class="kw">protected:
   class="type">class="kw">string            m_items[];
  };
Iterator<class="type">class="kw">string>* ConcreteAggregate::CreateIterator(class="type">void)
  {
   class="kw">return new ConcreteIterator<class="type">class="kw">string>(this);
  }
class="type">void ConcreteAggregate::class="kw">operator+=(class="type">class="kw">string item)
  {
   class="type">int size=ArraySize(m_items);
   ArrayResize(m_items,size+class="num">1);
   m_items[size]=item;
  }
class="type">class="kw">string ConcreteAggregate::class="kw">operator[](class="type">int at)
  {
   class="kw">return m_items[at];
  }
class="type">int ConcreteAggregate::Count()
  {
   class="kw">return ArraySize(m_items);
  }

用调解器给 EA 内部模块解耦

在 MT5 写复杂 EA 时,信号计算、仓位管理、风控模块往往要互相喊话。若让它们直接引用彼此,改动一个类就可能牵动一片,重用某个模块会变得很麻烦。调解器范式就是插一个中间人:所有同事对象只认中介,不认彼此,通信路径由中介集中路由。 这套结构有三个角色。Mediator 是通信接口;ConcreteMediator 持有各同事指针并协调行为;Colleague 类(如 ConcreteColleague1/2)只管把消息交给自己的中介,不直接调别人。这样对象协议变简单,合作被抽象,控制集中,子类化需求下降。 别把中介写成万能类。一个常见陷阱是让 ConcreteMediator 接管一切逻辑,结果它比同事还臃肿;正确做法是只管路由与必要协调,业务逻辑仍留在同事内。外汇与贵金属杠杆高,这类架构错误不会直接亏钱,但会让策略迭代更慢、bug 更难查。 下面这段 MQL5 骨架可直接塞进 .mqh 验证:同事 1 发消息,中介转给同事 2 的 Notify。你在 MT5 里建两个类各打印一句,就能看到解耦后的调用链。 代码逐行看:interface Colleague 只声明 Send,约束所有同事必须有发信能力;interface Mediator 的 Send 带 message 和发起者引用,中介据此判断转发目标;ConcreteColleague1 构造时绑 mediato r指针,Send 内调 m_mediator.Send(message,this),自身不认识同事 2;ConcreteMediator::Send 用 if(colleague_1==&colleague) 决定由 colleague_2 还是 colleague_1 收通知——集中式路由就这几行。

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

「同事类与中介者的消息路由实现」

在中介者模式里,具体同事类不直接互相调用,而是把消息交给中介者转发。下面这段 C++ 风格代码展示了两个同事类如何只持有一个中介者引用,发送时统一走 m_mediator.Send。 ConcreteColleague1 和 ConcreteColleague2 的构造都接收一个 Mediator 引用并保存为成员;它们的 Send 方法内部调用 m_mediator.Send(message, this),把自身指针传出去,自己不关心谁接收。 ConcreteMediator 持有 colleague_1 与 colleague_2 两个指针,其 Send 方法根据传入的 colleague 引用判断来源:若来自 colleague_1 则通知 colleague_2,否则通知 colleague_1。这种硬编码路由在仅两个对象的场景下够用,对象超过三个后维护成本会明显上升。 在 MT5 的 EA 架构里若套用此思路,可以把指标计算模块与下单模块做成同事类,由中介者统一协调,避免相互耦合导致的重编译连锁反应。外汇与贵金属杠杆交易高风险,任何架构优化都不改变仓位失控可能爆仓的概率。

MQL5 / C++
class ConcreteColleague1:class="kw">public Colleague
  {
class="kw">protected:
   Mediator*        m_mediator;
class="kw">public:
                   ConcreteColleague1(Mediator& mediator);
   class="type">void             Notify(class="type">class="kw">string message);
   class="type">void             Send(class="type">class="kw">string message);
  };
ConcreteColleague1::ConcreteColleague1(Mediator& meditor):
  m_mediator(&mediator)
  {
  }
class="type">void ConcreteColleague1::Notify(class="type">class="kw">string message)
  {
  }
class="type">void ConcreteColleague1::Send(class="type">class="kw">string message)
  {
  m_mediator.Send(message,this);
  }
class ConcreteColleague2:class="kw">public Colleague
  {
class="kw">protected:
   Mediator*        m_mediator;
class="kw">public:
                   ConcreteColleague2(Mediator& mediator);
   class="type">void             Notify(class="type">class="kw">string message);
   class="type">void             Send(class="type">class="kw">string message);
  };
ConcreteColleague2::ConcreteColleague2(Mediator& mediator):
  m_mediator(&mediator)
  {
  }
class="type">void ConcreteColleague2::Notify(class="type">class="kw">string message)
  {
  }
class="type">void ConcreteColleague2::Send(class="type">class="kw">string message)
  {
  m_mediator.Send(message,this);
  }
class ConcreteMediator:class="kw">public Mediator
  {
class="kw">public:
  ConcreteColleague1*  colleague_1;
  ConcreteColleague2*  colleague_2;
  class="type">void                 Send(class="type">class="kw">string message,Colleague& colleague);
  };
class="type">void ConcreteMediator::Send(class="type">class="kw">string message,Colleague& colleague)
  {
  if(colleague_1==&colleague)
     colleague_2.Notify(message);
  else
     colleague_1.Notify(message);
  }

◍ 别急着下结论

这套行为范式在 MT5 里真正落地的,是责任链、命令、解释器、迭代器、调解器五个。它们解决的是同一件事:把交易逻辑里「谁该处理信号」「怎么解耦指标与下单」「怎么遍历持仓」这类纠缠,拆成可替换的模块。外汇与贵金属杠杆高,范式再干净也挡不住滑点和黑天鹅,回测顺手不代表实盘能活。 附件里五个 .mqh 文件体积都不大,Chain_Of_Responsibility.mqh 3.52 KB、Iterator.mqh 仅 2.31 KB,直接拖进 MetaEditor 编译就能看到骨架。想验证迭代器写法,下面那段模板代码有三种 Next() 实现,第二种最危险——越界前不判 IsDone()。 [CODE] 里第三种把 m_current++ 收进 IsDone() 判断内,才是稳妥写法。开 MT5 自己改两行跑一遍,比读十篇综述有用。设计范式是工具不是圣杯,写完能编译、能回测、能亏得起,才算过关。

MQL5 / C++
<span class="keyword">class="kw">template</span>&lt;<span class="keyword">class="kw">typename</span> T&gt;
<span class="keyword">class="type">void</span> ConcreteIterator::Next(<span class="keyword">class="type">void</span>)
&nbsp;&nbsp;{
&nbsp;&nbsp; m_current++;
<span style="background-class="type">class="kw">color:rgb(class="num">255, class="num">246, class="num">200);">&nbsp;&nbsp; <span class="keyword">if</span>(!IsDone())
&nbsp;&nbsp;&nbsp;&nbsp; {
&nbsp;&nbsp;&nbsp;&nbsp; }</span>
&nbsp;&nbsp;}
<span class="keyword">class="kw">template</span>&lt;<span class="keyword">class="kw">typename</span> T&gt;
<span class="keyword">class="type">void</span> ConcreteIterator::Next(<span class="keyword">class="type">void</span>)
&nbsp;&nbsp;{
&nbsp;&nbsp; m_current++;
&nbsp;&nbsp;}
<span class="keyword">class="kw">template</span>&lt;<span class="keyword">class="kw">typename</span> T&gt;
<span class="keyword">class="type">void</span> ConcreteIterator::Next(<span class="keyword">class="type">void</span>)
&nbsp;&nbsp;{&nbsp;&nbsp; 
&nbsp;&nbsp; <span class="keyword">if</span>(!IsDone())
&nbsp;&nbsp;&nbsp;&nbsp; {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_current++;
&nbsp;&nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp;}

常见问题

用迭代器模式单独封装行情序列的遍历,主逻辑只管调用next(),不碰底层数组下标,后期改数据源只动迭代器。
在迭代器里加hasNext()判断,返回false就停循环,别靠外部数数,能从根上防越界报错。
小布可扫描你的策略结构,标出强依赖的模块并建议用调解器解耦,你照提示拆就行。
引中介者做消息路由,模块只发同事类消息给中介,不直接互调,出事也好查源头。
别急下结论,先查实盘tick量和异常处理差异,解耦本身没问题,多半是边界没兜住。