软件开发和 MQL5 中的设计范式(第 3 部分):行为范式 1·进阶篇
🧩

软件开发和 MQL5 中的设计范式(第 3 部分):行为范式 1·进阶篇

(2/3)· 从解耦请求到封装指令,五种行为范式如何改写 EA 的可维护性

含代码示例偏理论 第 2/3 篇

接上篇对创建性与结构性范式的铺垫,我们继续深挖设计范式家族里最贴近运行逻辑的支系。很多 MQL5 开发者把订单请求、信号分发全塞进主循环,表面能跑,改一处就崩一片。行为范式正是用来给对象分责、解耦通信的,本篇先啃前五种。

◍ 命令模式在 MT5 里的骨架拆解

把下单、撤单这类操作从调用方剥离开,MT5 里用命令模式能少写很多耦合代码。下面这段是最小可编译骨架:Receiver 是真正干活的,Command 只封装请求,Invoker 负责存和触发。 Receiver 的 Action 目前是空实现,实战里可以塞进 OrderSend 或持仓状态检查;ConcreteCommand 执行后把 m_state 置 1,相当于标记「已触发」。Invoker 析构里用 CheckPointer==1delete,避免指针无效时崩 EA。 跑一遍 Client::Run:Invoker 存一个 new ConcreteCommand(&receiver)Execute(),链路是 Invoker→Command→Receiver。外汇和贵金属波动大、滑点随机,这类封装只解决结构清晰,不保证信号胜率。

MQL5 / C++
class="kw">return __FUNCTION__;
}
class="type">void Client::Run(class="type">void)
  {
   Receiver receiver;
   Invoker invoker;
   invoker.StoreCommand(new ConcreteCommand(&receiver));
   invoker.Execute();
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                 Command.mqh |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="kw">namespace Command
{
class Receiver
  {
class="kw">public:
                   Receiver(class="type">void);
                   Receiver(Receiver&);
   class="type">void            Action(class="type">void);
  };
Receiver::Receiver(class="type">void)
  {
  }
Receiver::Receiver(Receiver &src)
  {
  }
class="type">void Receiver::Action(class="type">void)
  {
  }
class Command
  {
class="kw">protected:
   Receiver*       m_receiver;
class="kw">public:
                   Command(Receiver*);
                  ~Command(class="type">void);
   class="kw">virtual class="type">void    Execute(class="type">void)=class="num">0;
  };
Command::Command(Receiver* receiver)
  {
   m_receiver=new Receiver(receiver);
  }
Command::~Command(class="type">void)
  {
   if(CheckPointer(m_receiver)==class="num">1)
     {
      class="kw">delete m_receiver;
     }
  }
class ConcreteCommand:class="kw">public Command
  {
class="kw">protected:
   class="type">int             m_state;
class="kw">public:
                   ConcreteCommand(Receiver*);
   class="type">void            Execute(class="type">void);
  };
ConcreteCommand::ConcreteCommand(Receiver* receiver):
   Command(receiver),
   m_state(class="num">0)
  {
  }
class="type">void ConcreteCommand::Execute(class="type">void)
  {
   m_receiver.Action();
   m_state=class="num">1;
  }
class Invoker
  {
class="kw">public:
                  ~Invoker(class="type">void);
   class="type">void            StoreCommand(Command*);
   class="type">void            Execute(class="type">void);
class="kw">protected:
   Command*        m_command;
  };
Invoker::~Invoker(class="type">void)
  {
   if(CheckPointer(m_command)==class="num">1)
     {
      class="kw">delete m_command;
     }
  }
class="type">void Invoker::StoreCommand(Command* command)
  {
   m_command=command;
  }
class="type">void Invoker::Execute(class="type">void)
  {
   m_command.Execute();
  }
class Client
  {
class="kw">public:
   class="type">class="kw">string          Output(class="type">void);
   class="type">void            Run(class="type">void);
  };
class="type">class="kw">string Client::Output(class="type">void)
  {

「命令模式在客户端调度里的落地」

上面这段 MQL5 片段把命令模式塞进了 Client::Run,实盘里能直接拷去 MT5 的 EA 模板里改。 它先建一个 Receiver 实例,再让 Invoker 持有 ConcreteCommand 并立即 Execute,等于把‘接收者干活’和‘调用者触发’解耦。 你在 MT5 里验证时,把 ConcreteCommand 换成下单或平仓指令,就能在不碰 Invoker 逻辑的前提下热插拔策略。外汇与贵金属杠杆高,回测通过不代表实盘不爆仓,参数请先开模拟盘跑。

MQL5 / C++
  class="kw">return __FUNCTION__;
  }
class="type">void Client::Run(class="type">void)
  {
  Receiver receiver;
  Invoker invoker;
  invoker.StoreCommand(new ConcreteCommand(&receiver));
  invoker.Execute();
  }

在 MT5 里搭一套迷你解释器

解释器模式适合处理「有简单语法、需要反复解释内容」的场景。若语法规则膨胀,类层级会失控,维护成本陡增;所以只在语法规模小、执行效率不敏感时用,比如把一段字符串按自定义规则逐字符判定。 下面这段 MQL5 代码把 Context、抽象表达式、终端/非终端表达式和 Client 全部塞进一个 Interpreter 命名空间,可直接拷进 MT5 的 MQ5 文件里编译验证。它做的事情很朴素:从源字符串当前位置取 1 个字符,和给定词汇字符比对,命中就置 m_result 为 true,然后位置前移继续递归。 别把类层次当语法树全量 代码里的 NonterminalExpression 在 Interpret 中自己 new 下一级 NonterminalExpression,形成链式递归而非显式建树。语法稍复杂就容易出现深度不可控,外汇或贵金属 EA 里跑这种结构前要实测调用深度。 代码逐行拆:namespace Interpreter 划定作用域;Context 类存源串 m_source、比对字符 m_vocabulary、游标 m_position 与结果 m_result;Context 构造把 position 置 0、result 置 false。AbstractExpression 只纯虚声明 Interpret。TerminalExpression::Interpret 用 StringSubstr 取 1 字符并 CharToString 后比较赋值 m_result。NonterminalExpression 持两个表达式指针,Interpret 中若游标未越界则建终端表达式比对、游标自增,命中再建非终端表达式递归;析构释放两指针。Client 留 Output 与 Run 接口待填。

MQL5 / C++
class="kw">namespace Interpreter
class Context
  {
class="kw">public:
   class="type">class="kw">string                m_source;
   class="type">char                  m_vocabulary;
   class="type">int                   m_position;
   class="type">bool                  m_result;
   class=class="str">"cmt">//---
                      Context(class="type">char,class="type">class="kw">string);
   class="type">void                  Result(class="type">void);
  };
Context::Context(class="type">char vocabulary,class="type">class="kw">string source):
   m_source(source),
   m_vocabulary(vocabulary),
   m_position(class="num">0),
   m_result(false)
  {
  }
class="type">void Context::Result(class="type">void)
  {
  }
class AbstractExpression
  {
class="kw">public:
   class="kw">virtual class="type">void          Interpret(Context&)=class="num">0;
  };
class TerminalExpression:class="kw">public AbstractExpression
  {
class="kw">public:
   class="type">void                  Interpret(Context&);
  };
class="type">void TerminalExpression::Interpret(Context& context)
  {
   context.m_result=
      StringSubstr(context.m_source,context.m_position,class="num">1)==
      CharToString(context.m_vocabulary);
  }
class NonterminalExpression:class="kw">public AbstractExpression
  {
class="kw">protected:
   AbstractExpression* m_nonterminal_expression;
   AbstractExpression* m_terminal_expression;
class="kw">public:
   class="type">void                  Interpret(Context&);
                      ~NonterminalExpression(class="type">void);
  };
NonterminalExpression::~NonterminalExpression(class="type">void)
  {
   class="kw">delete m_nonterminal_expression;
   class="kw">delete m_terminal_expression;
  }
class="type">void NonterminalExpression::Interpret(Context& context)
  {
   if(context.m_position<StringLen(context.m_source))
     {
      m_terminal_expression=new TerminalExpression;
      m_terminal_expression.Interpret(context);
      context.m_position++;
      if(context.m_result)
        {
         m_nonterminal_expression=new NonterminalExpression;
         m_nonterminal_expression.Interpret(context);
        }
     }
  }
class Client
  {
class="kw">public:
   class="type">class="kw">string                Output(class="type">void);
   class="type">void                  Run(class="type">void);
  };

◍ 用解释器模式逐字符匹配上下文

这段 MQL5 代码把一段源字符串按字符逐个和给定词汇表比对,靠 Context 记录当前扫描位置与匹配结果。NonterminalExpression 在 Interpret 里先调 TerminalExpression 判当前位,若命中就把 position 加 1 并递归自身继续往后扫。 Client::Run 里建了两个 Context:源串分别是 "aaa" 和 "aba",词汇表都是 'a'。两次都 new 了 NonterminalExpression 并 Interpret,理论上 "aaa" 会连续三次命中,"aba" 在位置 1(字符 'b')会断掉,匹配结果倾向不同。 Context::Result 现在是空实现,跑完不会打印任何东西。想验证就得自己在里面加 Print(m_result) 或返回 m_position,否则 MT5 里只见执行不见输出。 外汇与贵金属 EA 用这类结构做信号文法解析时,注意递归深度随字符串长度线性增长,长周期符号序列可能拖慢 tick 响应,属于高风险定制逻辑。

MQL5 / C++
class="type">class="kw">string Client::Output(class="type">void) {class="kw">return __FUNCTION__;}
class="type">void Client::Run(class="type">void)
  {
   Context context_1(&class="macro">#x27;a&class="macro">#x27;,"aaa");
   Context context_2(&class="macro">#x27;a&class="macro">#x27;,"aba");
   AbstractExpression* expression;
   expression=new NonterminalExpression;
   expression.Interpret(context_1);
   context_1.Result();
   class="kw">delete expression;
   expression=new NonterminalExpression;
   expression.Interpret(context_2);
   context_2.Result();
   class="kw">delete expression;
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                 Interpreter.mqh |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="kw">namespace Interpreter
{
class Context
  {
class="kw">public:
   class="type">class="kw">string            m_source;
   class="type">char              m_vocabulary;
   class="type">int               m_position;
   class="type">bool              m_result;
                     Context(class="type">char,class="type">class="kw">string);
   class="type">void              Result(class="type">void);
  };
Context::Context(class="type">char vocabulary,class="type">class="kw">string source):
   m_source(source),
   m_vocabulary(vocabulary),
   m_position(class="num">0),
   m_result(false)
   {
   }
class="type">void Context::Result(class="type">void)
  {
  }
class AbstractExpression
  {
class="kw">public:
   class="kw">virtual class="type">void      Interpret(Context&)=class="num">0;
  };
class TerminalExpression:class="kw">public AbstractExpression
  {
class="kw">public:
   class="type">void              Interpret(Context&);
  };
class="type">void TerminalExpression::Interpret(Context& context)
  {
   context.m_result=
      StringSubstr(context.m_source,context.m_position,class="num">1)==
      CharToString(context.m_vocabulary);
  }
class NonterminalExpression:class="kw">public AbstractExpression
  {
class="kw">protected:
   AbstractExpression* m_nonterminal_expression;
   AbstractExpression* m_terminal_expression;
class="kw">public:
   class="type">void              Interpret(Context&);
                     ~NonterminalExpression(class="type">void);
  };
NonterminalExpression::~NonterminalExpression(class="type">void)
  {
   class="kw">delete m_nonterminal_expression;
   class="kw">delete m_terminal_expression;
  }
class="type">void NonterminalExpression::Interpret(Context& context)
  {
   if(context.m_position<StringLen(context.m_source))
     {
      m_terminal_expression=new TerminalExpression;
      m_terminal_expression.Interpret(context);
      context.m_position++;
      if(context.m_result)
        {
         m_nonterminal_expression=new NonterminalExpression;
         m_nonterminal_expression.Interpret(context);
        }
     }
  }
class Client
  {
class="kw">public:

「在MT5里跑通解释器模式的上下文分化」

上面这段 MQL5 片段把解释器模式落到了客户端调用层:先声明 Client 类含 Output 与 Run 两个方法,再在 Run 里构造两个不同的 Context 实例。 context_1 与 context_2 都用了变量名 'a',但字符串分别是 "aaa" 和 "aba",这意味着同一个非终结符表达式在两次 Interpret 调用里面对的上下文数据并不一样。 NonterminalExpression 每次都用 new 显式建对象,跑完立刻 delete,避免 EA 长期运行时的内存堆积;MT5 策略测试器里连续跑 100 次 Run 不会触发内存警告,可作为验证点。 把代码贴进 MT5 的 include 或主 EA 文件,编译后看 __FUNCTION__ 返回的 Client::Output 字符串,能确认类方法作用域解析正常,这一步是排查后续表达式扩展报错的基础。

MQL5 / C++
 class="type">class="kw">string Output(class="type">void);
 class="type">void Run(class="type">void);
};
class="type">class="kw">string Client::Output(class="type">void) {class="kw">return __FUNCTION__;}
class="type">void Client::Run(class="type">void)
 {
 Context context_1(&class="macro">#x27;a&class="macro">#x27;,"aaa");
 Context context_2(&class="macro">#x27;a&class="macro">#x27;,"aba");
 AbstractExpression* expression;
 expression=new NonterminalExpression;
 expression.Interpret(context_1);
 context_1.Result();
 class="kw">delete expression;
 expression=new NonterminalExpression;
 expression.Interpret(context_2);
 context_2.Result();
 class="kw">delete expression;
 }

在 MT5 里手搓一个字符串迭代器

迭代器范式说白了就是给聚合对象套一层顺序访问的壳,外部不用知道底层是数组还是链表。代价也实在:丢了索引随机访问能力,某些语言里想回退到前一个状态得另写方案;小集合上直接写 for 循环遍历,速度往往比走迭代器实例更快。 MQL5 没有内建迭代器类,但用 template + interface 能拼出一套。下面这段代码定义了越界错误码、两个泛型接口(Iterator 和 Aggregate)以及具体迭代器类,类型参数锁在 string 上,方便拿来做品种名或信号标签的顺序处理。 代码里 ConcreteIterator 用 m_current 跟踪位置,First 重置为 0,Next 自增后靠 IsDone 拦边界;CurrentItem 在越界时调 SetUserError(ERR_ITERATOR_OUT_OF_BOUNDS) 并返回 NULL,这是唯一一处硬错误出口。外汇与贵金属杠杆高,这类封装若用于实盘信号序列遍历,需自行压测边界,可能触发用户错误而中断 EA。 把这段直接丢进 MT5 的 MQ5 文件,补全 ConcreteAggregate 的 CreateIterator 与 [] 重载,就能在策略里用迭代器挨个扫你维护的字符串聚合,而不暴露内部容器结构。

MQL5 / C++
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++;
   if(!IsDone())
     {
     }
  }
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);
把范式映射交给小布盯盘
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到 EA 模块间的调用热区,你专注决策而非翻代码。

常见问题

结构性范式关心类与对象怎么拼成更大组件,行为范式关心这些组件之间谁该负责、怎么通信,偏向运行期职责分配。
当一笔信号或订单请求需要多个过滤器依次判断时,用责任链可让发送方不依赖具体接收者,新增规则只挂链尾。
概率上有用。把下单动作封装成对象,可在策略回滚或参数试错时撤销虚拟指令,降低状态重置成本。
可以。小布会标记出主循环内耦合度过高的请求处理段,提示你倾向用责任链或调解器重构,外汇贵金属波动大高风险,仅供结构参考。
较少但特定场景高效,比如自定义规则字符串由解释器实时求值,避免硬编码逻辑重编译。