MQL5 设计范式行为篇(基础):用 6 类模式理顺对象通信(基础篇)
(1/3)· 责任链之后还剩哪些行为范式?Memento 到 Visitor 一次讲透
把行为范式塞进 MQL5 事件循环
MQL5 不是传统顺序脚本语言,它的 EA 生命周期由终端事件驱动。把「行为范式」落进代码,核心是把交易决策拆成可响应 OnTick / OnTradeTransaction 的独立行为单元,而不是写一长串 if-else 顺推。
- 年 7 月 MetaTrader 5 官方文档更新后,行为式 EA 模板在社群被引用 795 次讨论帖,说明老手也在重构成事件响应结构。外汇与贵金属杠杆高,事件遗漏可能导致重复开仓,回测前务必在策略测试器开「每 tick」模式验证逻辑。
下面这段骨架演示了如何把「价格穿越均线」做成行为而非过程:
「行为范式收尾要补齐的六块拼图」
前几篇已经把创建、结构两类范式拆完,行为范式也讲了责任链、命令、解释器、迭代器、调解器五种。这一节要收掉剩下的六种:Memento 负责把对象内部状态外部化存储后再恢复,且不动封装;Observer 建立一对多依赖,状态一变所有观察者自动收到通知;State 让对象在内部状态切换时表现得像换了类。 Strategy 把算法家族封装成可互换模块,客户端选策略时不必改主体逻辑;Template Method 只定算法骨架,留几步给子类重写;Visitor 则能在不碰元素类的前提下加新操作。 文中沿用一贯拆解路径:先说范式作用,再讲解决什么问题,最后落回 MQL5 怎么写。若你读过《解读 MQL5 面向对象编程》那篇,理解这些会快很多——OOP 基础直接决定你能不能把范式用干净。 外汇与贵金属 EA 开发用这些范式做状态管理或通知机制时,注意 MT5 实盘环境的高风险:逻辑再优雅也拦不住滑点和断连,回测通过不等于实盘稳健。
◍ 用令牌把状态快照藏到对象外
备忘录范式(业内也叫令牌)干的事很直接:把对象内部状态抽到外部存着,需要时再塞回去实现回滚。当对象的状态接口一暴露就会破封装,或者你就是想留个快照以后恢复,它就派上用场。三个角色分工明确——Originator 负责造快照和恢复,Memento 只装状态且只对 Originator 开放,Caretaker 只管存、绝不翻看内容。 这套打法在 MT5 里不是免费午餐。大对象复制快照会很吃内存;如果只留有限卡槽,老历史会被覆盖掉。它除快照外不泄露任何内部细节,封装边界保住了,但也意味着你没法半路偷看中间态。 下面这段 MQL5 是最小可跑骨架。Memento 用 protected 的 m_state 存字符串状态,对外只给 Get/Set;Originator 的 CreateMemento 用 new 返回快照指针,SetMemento 接收引用还原;Caretaker 析构里用 CheckPointer 判 1 才 delete,避免野指针。 别把快照当无限历史 实盘里若每根 Tick 都存 Memento,EURUSD 一秒十几跳就可能堆出上千对象,内存和 GC 压力会肉眼可见地拖慢 EA。建议只在关键事件(如挂单成交、止损移动)才 CreateMemento,卡槽控制在 10 个以内循环覆盖。
class Memento { class="kw">protected: class="type">class="kw">string m_state; class="kw">public: class="type">class="kw">string GetState(class="type">void); class="type">void SetState(class="type">class="kw">string); Memento(class="type">class="kw">string); }; Memento::Memento(class="type">class="kw">string state): m_state(state) { } class="type">class="kw">string Memento::GetState(class="type">void) { class="kw">return m_state; } class="type">void Memento::SetState(class="type">class="kw">string state) { m_state=state; } class Originator { class="kw">protected: class="type">class="kw">string m_state; class="kw">public: class="type">void SetMemento(Memento& memento); Memento* CreateMemento(class="type">void); class="type">class="kw">string State(class="type">void); class="type">void State(class="type">class="kw">string); }; class="type">void Originator::SetMemento(Memento& memento) { m_state=memento.GetState(); } Memento* Originator::CreateMemento(class="type">void) { class="kw">return new Memento(m_state); } class="type">void Originator::State(class="type">class="kw">string state) { m_state=state; } class="type">class="kw">string Originator::State(class="type">void) { class="kw">return m_state; } class Caretaker { class="kw">public: Memento* memento; ~Caretaker(class="type">void); }; Caretaker::~Caretaker(class="type">void) { if(CheckPointer(memento)==class="num">1) { class="kw">delete memento; } }
用备忘录模式冻结策略状态
在 MT5 的 EA 架构里,想把某一刻的策略状态(比如持仓逻辑标记、波段判定字符串)存下来稍后回滚,备忘录模式比全局变量更干净。Originator 负责把自己的 m_state 打包成 Memento,也能从外部 Memento 还原。 Caretaker 只持有一个 Memento 指针,不关心内部字段。析构时用 CheckPointer(memento)==1 判断对象是否有效再 delete,避免重复释放导致终端崩溃。 实盘里可以每根 K 线收盘把 State 写入 Memento,遇到异常信号再 SetMemento 回退到上一根的状态,外汇与贵金属波动剧烈,这类回退可能降低连错概率,但不构成任何收益保证。
class="kw">protected: class="type">class="kw">string m_state; class="kw">public: class="type">void SetMemento(Memento& memento); Memento* CreateMemento(class="type">void); class="type">class="kw">string State(class="type">void); class="type">void State(class="type">class="kw">string); }; class="type">void Originator::SetMemento(Memento& memento) { m_state=memento.GetState(); } Memento* Originator::CreateMemento(class="type">void) { class="kw">return new Memento(m_state); } class="type">void Originator::State(class="type">class="kw">string state) { m_state=state; } class="type">class="kw">string Originator::State(class="type">void) { class="kw">return m_state; } class Caretaker { class="kw">public: Memento* memento; ~Caretaker(class="type">void); }; Caretaker::~Caretaker(class="type">void) { if(CheckPointer(memento)==class="num">1) { class="kw">delete memento; } }
「MT5里用观察者解耦行情推送」
观察者范式本质是一对多的状态广播:一个主题对象状态变了,所有挂在其上的观察者自动收到通知并更新。在EA或指标里,这适合把『行情判定』和『通知动作』拆开——比如一个类专门算突破,另一个类专门发Alert或写日志,彼此不硬耦合。 用它的代价也得看清:主题不知道到底是谁改了状态,观察者一多就容易出现大批量更新,调试时调用链不直观。外汇和贵金属波动快、滑点随机,这类广播链若叠太多层,可能拖慢OnTick响应,实盘前务必在策略测试器跑一遍。 下面这段MQL5把范式落到了可编译的骨架。interface Observer只定义一个Update(string state),主题类用m_observers[]存指针,Attach加人、Detach摘人、Notify循环调Update。 代码逐行看:Subject构造函数把m_state置为NULL;析构函数遍历数组,用CheckPointer==1确认有效指针后delete,避免内存泄漏。Notify里直接m_observers[i].Update(m_state)完成推送;Attach用ArrayResize扩一位挂新观察者;Detach先Find定位,删指针再ArrayRemove。Find靠指针地址比对返回索引,找不到回-1。
interface Observer
{
class="type">void Update(class="type">class="kw">string state);
};
class Subject
{
class="kw">public:
Subject(class="type">void);
~Subject(class="type">void);
class="type">void Attach(Observer* observer);
class="type">void Detach(Observer& observer);
class="type">void Notify(class="type">void);
class="type">void State(class="type">class="kw">string state);
class="type">class="kw">string State(class="type">void) {class="kw">return m_state;}
class="kw">protected:
class="type">class="kw">string m_state;
Observer* m_observers[];
class="type">int Find(Observer& observer);
};
Subject::Subject(class="type">void):
m_state(NULL)
{
}
Subject::~Subject(class="type">void)
{
class="type">int itotal=ArraySize(m_observers);
for(class="type">int i=class="num">0; i<itotal; i++)
{
Observer* item=m_observers[i];
if(CheckPointer(item)==class="num">1)
{
class="kw">delete item;
}
}
}
class="type">void Subject::State(class="type">class="kw">string state)
{
m_state=state;
}
class="type">void Subject::Notify(class="type">void)
{
class="type">int itotal=ArraySize(m_observers);
for(class="type">int i=class="num">0; i<itotal; i++)
{
m_observers[i].Update(m_state);
}
}
class="type">void Subject::Attach(Observer *observer)
{
class="type">int size=ArraySize(m_observers);
ArrayResize(m_observers,size+class="num">1);
m_observers[size]=observer;
}
class="type">void Subject::Detach(Observer &observer)
{
class="type">int find=Find(observer);
if(find==-class="num">1)
class="kw">return;
Observer* item=m_observers[find];
if(CheckPointer(item)==class="num">1)
class="kw">delete item;
ArrayRemove(m_observers,find,class="num">1);
}
class="type">int Subject::Find(Observer &observer)
{
class="type">int itotal=ArraySize(m_observers);
for(class="type">int i=class="num">0; i<itotal; i++)
{
Observer* item=m_observers[i];
if(item==&observer)
class="kw">return i;
}◍ 把观察者模式跑进EA里
上面这段 MQL5 类结构把经典的观察者模式落地成了可直接编译的头文件骨架:主题(Subject)持有状态字符串与观察者指针数组,任何状态变更后调 Notify 就能把新状态推给所有挂接的观察者。 ConcreteObserver 在构造时绑定一个 ConcreteSubject 引用,Update 被调用时仅把传入的 state 存进自己的 m_observer_state;这意味着你能在不改动主题逻辑的前提下,挂多个观察者分别写日志、发警报或画对象。 Subject 析构里用 ArraySize 拿到数组长度后循环 delete 指针,且以 CheckPointer 返回值等于 1 判定有效对象才释放——漏掉这步在 MT5 里会悄悄内存泄漏。开 MT5 建个 mqh 把下面代码贴进去,再写个脚本来 Attach 两个观察者、改一次 State 看是否都收到,就能验证整套机制。 外汇与贵金属行情驱动这类事件链时波动剧烈,观察者回调若做了重计算可能拖慢 tick 处理,实盘前务必在策略测试器跑满不同点差环境。
class="kw">return -class="num">1; } class ConcreteSubject:class="kw">public Subject { class="kw">public: class="type">void State(class="type">class="kw">string state); class="type">class="kw">string State(class="type">void) {class="kw">return m_state;} }; class="type">void ConcreteSubject::State(class="type">class="kw">string state) { m_state=state; } class ConcreteObserver:class="kw">public Observer { class="kw">public: class="type">void Update(class="type">class="kw">string state); ConcreteObserver(ConcreteSubject& subject); class="kw">protected: class="type">class="kw">string m_observer_state; ConcreteSubject* m_subject; }; ConcreteObserver::ConcreteObserver(ConcreteSubject& subject): m_subject(&subject) { } class="type">void ConcreteObserver::Update(class="type">class="kw">string state) { m_observer_state=state; } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Observer.mqh | class=class="str">"cmt">//+------------------------------------------------------------------+ interface Observer { class="type">void Update(class="type">class="kw">string state); }; class Subject { class="kw">public: Subject(class="type">void); ~Subject(class="type">void); class="type">void Attach(Observer* observer); class="type">void Detach(Observer& observer); class="type">void Notify(class="type">void); class="type">void State(class="type">class="kw">string state); class="type">class="kw">string State(class="type">void) {class="kw">return m_state;} class="kw">protected: class="type">class="kw">string m_state; Observer* m_observers[]; class="type">int Find(Observer& observer); }; Subject::Subject(class="type">void): m_state(NULL) { } Subject::~Subject(class="type">void) { class="type">int itotal=ArraySize(m_observers); for(class="type">int i=class="num">0; i<itotal; i++) { Observer* item=m_observers[i]; if(CheckPointer(item)==class="num">1) { class="kw">delete item; } } } class="type">void Subject::State(class="type">class="kw">string state) { m_state=state; } class="type">void Subject::Notify(class="type">void) { class="type">int itotal=ArraySize(m_observers); for(class="type">int i=class="num">0; i<itotal; i++) { m_observers[i].Update(m_state); } } class="type">void Subject::Attach(Observer *observer) { class="type">int size=ArraySize(m_observers);