软件开发和 MQL5 中的设计范式(第 3 部分):行为范式 1·综合运用
「用迭代器模式隔离行情序列的遍历逻辑」
在 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 线的高点字符串聚合,用迭代器打印,能直观验证遍历与容器已解耦。外汇与贵金属杠杆高,这类结构重构只解决代码维护成本,不暗示任何胜率改善。
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 把这段贴进脚本,故意多循环一次就能看到错误码触发。
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 收通知——集中式路由就这几行。
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 架构里若套用此思路,可以把指标计算模块与下单模块做成同事类,由中介者统一协调,避免相互耦合导致的重编译连锁反应。外汇与贵金属杠杆交易高风险,任何架构优化都不改变仓位失控可能爆仓的概率。
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 自己改两行跑一遍,比读十篇综述有用。设计范式是工具不是圣杯,写完能编译、能回测、能亏得起,才算过关。
<span class="keyword">class="kw">template</span><<span class="keyword">class="kw">typename</span> T> <span class="keyword">class="type">void</span> ConcreteIterator::Next(<span class="keyword">class="type">void</span>) { m_current++; <span style="background-class="type">class="kw">color:rgb(class="num">255, class="num">246, class="num">200);"> <span class="keyword">if</span>(!IsDone()) { }</span> } <span class="keyword">class="kw">template</span><<span class="keyword">class="kw">typename</span> T> <span class="keyword">class="type">void</span> ConcreteIterator::Next(<span class="keyword">class="type">void</span>) { m_current++; } <span class="keyword">class="kw">template</span><<span class="keyword">class="kw">typename</span> T> <span class="keyword">class="type">void</span> ConcreteIterator::Next(<span class="keyword">class="type">void</span>) { <span class="keyword">if</span>(!IsDone()) { m_current++; } }