MQL5 设计范式行为篇(基础):用 6 类模式理顺对象通信(基础篇)
🧩

MQL5 设计范式行为篇(基础):用 6 类模式理顺对象通信(基础篇)

(1/3)· 责任链之后还剩哪些行为范式?Memento 到 Visitor 一次讲透

实战向进阶 第 1/3 篇
很多 MQL5 开发者把状态恢复和通知逻辑写死在 EA 主循环里,一旦品种或周期切换就崩。行为范式不是教科书名词,而是你绕开这类耦合坑的现成路线。

把行为范式塞进 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 个以内循环覆盖。

MQL5 / C++
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 回退到上一根的状态,外汇与贵金属波动剧烈,这类回退可能降低连错概率,但不构成任何收益保证。

MQL5 / C++
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。

MQL5 / C++
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 处理,实盘前务必在策略测试器跑满不同点差环境。

MQL5 / C++
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);
把范式巡检交给小布盯盘
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到代码耦合热区,你专注决策哪些模块该抽成策略类。

常见问题

通过窄接口暴露快照、宽接口还原,将状态对象与 Originator 分离,回测中可安全回溯订单上下文。
概率上会,建议用事件过滤或合并通知,只在关键状态跃迁时推送给依赖者。
State 改的是对象自身行为像换了类,Strategy 是客户端主动换算法族,前者偏内部、后者偏外部调用。
可以,AIGC 模块对持仓管理类做静态扫描,倾向列出命令与状态混杂的函数的改写建议。
可在不碰原有缓冲计算类的前提下挂接导出、序列化等逻辑,降低多周期适配的改动面。