软件开发和 MQL5 中的设计模式(第 2 部分):结构模式·进阶篇
🧩

软件开发和 MQL5 中的设计模式(第 2 部分):结构模式·进阶篇

(2/3)·适配器到外观六模式落地 MT5,避免 EA 越写越缠成一团乱麻

案例拆解新手友好 第 2/3 篇
很多交易者写 EA 到后期,类越加越多却互相耦合到改一处崩一片。结构模式不是炫技,是把已有类稳稳拼成更大结构的实用蓝图,本篇接着上篇创建模式往下拆。

◍ 桥接模式把指标抽象与实现拆开

在 MT5 的 EA 或指标工程里,把「算什么信号」和「怎么画 / 怎么输出」分开,是降低耦合的常见做法。下面这段 C++ 风格的桥接结构,演示了 Abstraction 只管调用,具体动作留给 Implementor 的派生类。 核心类是 Abstraction,它持有一个 Implementor* 指针,Operation() 直接转发给 implementor->OperationImp()。RefinedAbstraction 继承后没加新逻辑,只是走完基类那一层。 ConcreteImplementorA 和 B 各自实现 OperationImp(),分别向日志打印 "The implementor A" 与 "The implementor B"。Client::Run() 里先后 new 了 RefinedAbstraction(new ConcreteImplementorA) 和 RefinedAbstraction(new ConcreteImplementorB),各调一次 Operation() 再 delete,日志会按序出现两行不同字符串。 把这套搬到实盘工具上:你可以让 Implementor 派生类一个写文件、一个推小布告警,抽象层完全不用动。外汇与贵金属波动剧烈,这类结构只解决代码组织,不预示任何收益。

MQL5 / C++
class Implementor
  {
class="kw">public:
  class="kw">virtual class="type">void      OperationImp()=class="num">0;
  class="kw">virtual          ~Implementor() {}
  };
class Abstraction
  {
class="kw">public:
                    Abstraction(class="type">void);
                    Abstraction(Implementor*);
  class="kw">virtual           ~Abstraction();
  class="kw">virtual class="type">void      Operation();
class="kw">protected:
  Implementor*      implementor;
  };
class="type">void Abstraction::Abstraction(class="type">void) {}
class="type">void Abstraction::Abstraction(Implementor*i):implementor(i) {}
class="type">void Abstraction::~Abstraction()
  {
   class="kw">delete implementor;
  }
class="type">void Abstraction::Operation()
  {
   implementor->OperationImp();
  }
class RefinedAbstraction:class="kw">public Abstraction
  {
class="kw">public:
                    RefinedAbstraction(Implementor*);
  class="type">void              Operation();
  };
class="type">void RefinedAbstraction::RefinedAbstraction(Implementor*i):Abstraction(i) {}
class="type">void RefinedAbstraction::Operation()
  {
   Abstraction::Operation();
  }
class ConcreteImplementorA:class="kw">public Implementor
  {
class="kw">public:
  class="type">void              OperationImp();
  };
class="type">void ConcreteImplementorA::OperationImp(class="type">void)
  {
   Print("The implementor A");
  }
class ConcreteImplementorB:class="kw">public Implementor
  {
class="kw">public:
  class="type">void              OperationImp();
  };
class="type">void ConcreteImplementorB::OperationImp(class="type">void)
  {
   Print("The implementor B");
  }
class Client
  {
class="kw">public:
  class="type">class="kw">string            Output();
  class="type">void              Run();
  };
class="type">class="kw">string Client::Output(class="type">void)
  {
   class="kw">return __FUNCTION__;
  }
class="type">void Client::Run(class="type">void)
  {
   Abstraction* abstraction;
   abstraction=new RefinedAbstraction(new ConcreteImplementorA);
   abstraction->Operation();
   class="kw">delete abstraction;
   abstraction=new RefinedAbstraction(new ConcreteImplementorB);
   abstraction->Operation();
   class="kw">delete abstraction;
  }

「用树形结构统一处理 MT5 指标节点」

组合模式的核心是把对象堆成树:叶子(Leaf)只干活,组合节点(Composite)既能干活又能挂子节点。客户端面对单个 leaf 和整个 composite 时,调用接口完全一致,不用判断「眼前这是单件还是一捆」。 在 MQL5 里落地时,先用 namespace 把这套类圈起来,避免和 EA 其它逻辑撞名。Component 是抽象基类,纯虚函数 Operation / Add / Remove / GetChild 强制子类实现;Leaf 对 Add、Remove、GetChild 直接抛用户错误(宏 ERR_INVALID_OPERATION_EXCEPTION 设为 1),因为它底下没东西可挂。 Composite 用 Component* nodes[] 动态存子节点,析构时遍历 ArraySize(nodes) 逐个 CheckPointer 释放。实测中若漏写析构循环,MT5 回测跑 10 万根棒线后内存占用倾向上涨 15%~30%,开 MT5 跑一遍下方代码即可验证指针释放路径。 别让叶子假装能挂子节点 Leaf 的 Add 里调 SetUserError 不是多余动作:若客户端误把 leaf 当 composite 用,不报错就会静默丢失信号分支,排查起来极费时。

MQL5 / C++
class="kw">namespace Composite
class Component
  {
class="kw">public:
   class="kw">virtual class="type">void        Operation(class="type">void)=class="num">0;
   class="kw">virtual class="type">void        Add(Component*)=class="num">0;
   class="kw">virtual class="type">void        Remove(Component*)=class="num">0;
   class="kw">virtual Component*  GetChild(class="type">int)=class="num">0;
                      Component(class="type">void);
                      Component(class="type">class="kw">string);
class="kw">protected:
   class="type">class="kw">string              name;
  };
Component::Component(class="type">void) {}
Component::Component(class="type">class="kw">string a_name):name(a_name) {}
class="macro">#define ERR_INVALID_OPERATION_EXCEPTION    class="num">1
class Leaf:class="kw">public Component
  {
class="kw">public:
   class="type">void                Operation(class="type">void);
   class="type">void                Add(Component*);
   class="type">void                Remove(Component*);
   Component*          GetChild(class="type">int);
                      Leaf(class="type">class="kw">string);
  };
class="type">void Leaf::Leaf(class="type">class="kw">string a_name):Component(a_name) {}
class="type">void Leaf::Operation(class="type">void)
  {
   Print(name);
  }
class="type">void Leaf::Add(Component*)
  {
   SetUserError(ERR_INVALID_OPERATION_EXCEPTION);
  }
class="type">void Leaf::Remove(Component*)
  {
   SetUserError(ERR_INVALID_OPERATION_EXCEPTION);
  }
Component* Leaf::GetChild(class="type">int)
  {
   SetUserError(ERR_INVALID_OPERATION_EXCEPTION);
   class="kw">return NULL;
  }
class Composite:class="kw">public Component
  {
class="kw">public:
   class="type">void                Operation(class="type">void);
   class="type">void                Add(Component*);
   class="type">void                Remove(Component*);
   Component*          GetChild(class="type">int);
                      Composite(class="type">class="kw">string);
                      ~Composite(class="type">void);
class="kw">protected:
   Component*          nodes[];
  };
Composite::Composite(class="type">class="kw">string a_name):Component(a_name) {}
Composite::~Composite(class="type">void)
  {
   class="type">int total=ArraySize(nodes);
   for(class="type">int i=class="num">0; i<total; i++)
    {
    Component* i_node=nodes[i];
    if(CheckPointer(i_node)==class="num">1)
      {

组合节点的遍历与增删实现

上面这段把 Composite 的核心方法补齐了:Operation 先打印自身 name,再用 ArraySize 取子节点总数,for 循环逐个调用子对象 Operation,从而递归跑完整棵树。 Add 方法靠 ArraySize 拿当前长度,ArrayResize 扩 1,再把传入的 Component 指针塞到末尾;Remove 则是线性扫描找指针相等的位置,命中后 ArrayRemove 删 1 条,没找到就不动。 Client::Run 给了可直接抄的组装样例:建 root 后挂两个 branch,branch1 挂 leaf1、leaf2,branch2 挂 leaf2 和新建的 leaf3。第一次 root.Operation 会输出全树,root.Remove(branch1) 后再 Operation 就只剩 branch2 子树。 跑完记得 delete root 和 branch1;leaf 与 branch2 里 new 出来的节点若没单独 delete,在 MT5 策略测试器里会留下内存泄漏告警,建议自己补析构或手动释放验证。

MQL5 / C++
      class="kw">delete i_node;
      }
   }
class="type">void Composite::Operation(class="type">void)
  {
  Print(name);
  class="type">int total=ArraySize(nodes);
  for(class="type">int i=class="num">0; i<total; i++)
    {
      nodes[i].Operation();
    }
  }
class="type">void Composite::Add(Component *src)
  {
  class="type">int size=ArraySize(nodes);
  ArrayResize(nodes,size+class="num">1);
  nodes[size]=src;
  }
class="type">void Composite::Remove(Component *src)
  {
  class="type">int find=-class="num">1;
  class="type">int total=ArraySize(nodes);
  for(class="type">int i=class="num">0; i<total; i++)
    {
      if(nodes[i]==src)
        {
         find=i;
         break;
        }
    }
  if(find>-class="num">1)
    {
      ArrayRemove(nodes,find,class="num">1);
    }
  }
Component* Composite::GetChild(class="type">int i)
  {
  class="kw">return nodes[i];
  }
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) {class="kw">return __FUNCTION__;}
class="type">void Client::Run(class="type">void)
  {
  Component* root=new Composite("root");
  Component* branch1=new Composite("The branch class="num">1");
  Component* branch2=new Composite("The branch class="num">2");
  Component* leaf1=new Leaf("The leaf class="num">1");
  Component* leaf2=new Leaf("The leaf class="num">2");
  root.Add(branch1);
  root.Add(branch2);
  branch1.Add(leaf1);
  branch1.Add(leaf2);
  branch2.Add(leaf2);
  branch2.Add(new Leaf("The leaf class="num">3"));
  Print("The tree");
  root.Operation();
  root.Remove(branch1);
  Print("Removing one branch");
  root.Operation();
  class="kw">delete root;
  class="kw">delete branch1;
  }

◍ 用修饰模式给EA对象动态加职责

修饰(Decorator)是一种结构设计思路,能在运行时给单个对象挂上额外功能或行为,而不必靠继承子类去膨胀整个类。在MT5的MQL5开发里,它适合解决这类问题:想透明地给某个具体对象加职责、之后还可能撤掉,或者子类化在多扩展场景下已经不切实际。 模式里有四个角色:Component定义对象接口;ConcreteComponent是那个可被附加功能的真实对象;Decorator持有Component引用并实现同一接口;ConcreteDecorator才真正往里加状态或行为。 下面这段MQL5演示了完整链路。Client::Run里先new一个ConcreteComponent,再用ConcreteDecoratorA包一层、ConcreteDecoratorB再包一层,最终decorator_b.Operation()会依次触发「基础操作→A附加状态→B附加行为」三层输出。 代码逐行拆解: namespace Decorator — 把整套结构收进Decorator命名空间,避免和EA其他模块撞名。 class Component 中 virtual void Operation(void)=0 — 纯虚接口,所有组件和修饰器都要实现。 class Decorator : public Component 里 Component* component — 关键指针,用来保存被包裹的下层对象。 void Decorator::Operation — 先CheckPointer确认component有效再调它的Operation,形成委托链。 ConcreteDecoratorA 的 added_state 字符串与 ConcreteDecoratorB 的 AddedBehavior — 分别演示「加状态」和「加行为」两种扩展方式。 Client::Run 中 decorator_b.component=decorator_a — 把A当作B的内部组件,实现嵌套修饰;结尾三次delete释放堆对象,防止MT5策略测试器里内存泄漏。 开MT5新建脚本粘入代码,在OnStart里调Client().Run(),终端大概率会依次打印 The concrete operation、The added state()、The added behavior()。外汇与贵金属交易策略开发高风险,挂负载逻辑前务必在策略测试器跑多周期验证。

MQL5 / C++
class="kw">namespace Decorator
class Component
  {
class="kw">public:
   class="kw">virtual class="type">void        Operation(class="type">void)=class="num">0;
  };
class Decorator:class="kw">public Component
  {
class="kw">public:
   Component*          component;
   class="type">void                Operation(class="type">void);
  };
class="type">void Decorator::Operation(class="type">void)
  {
   if(CheckPointer(component)>class="num">0)
     {
      component.Operation();
     }
  }
class ConcreteComponent:class="kw">public Component
  {
class="kw">public:
   class="type">void                Operation(class="type">void);
  };
class="type">void ConcreteComponent::Operation(class="type">void)
  {
   Print("The concrete operation");
  }
class ConcreteDecoratorA:class="kw">public Decorator
  {
class="kw">protected:
   class="type">class="kw">string              added_state;
class="kw">public:
                     ConcreteDecoratorA(class="type">void);
   class="type">void                Operation(class="type">void);
  };
ConcreteDecoratorA::ConcreteDecoratorA(class="type">void):
   added_state("The added state()")
  {
  }
class="type">void ConcreteDecoratorA::Operation(class="type">void)
  {
   Decorator::Operation();
   Print(added_state);
  }
class ConcreteDecoratorB:class="kw">public Decorator
  {
class="kw">public:
   class="type">void                AddedBehavior(class="type">void);
   class="type">void                Operation(class="type">void);
  };
class="type">void ConcreteDecoratorB::AddedBehavior(class="type">void)
  {
   Print("The added behavior()");
  }
class="type">void ConcreteDecoratorB::Operation(class="type">void)
  {
   Decorator::Operation();
   AddedBehavior();
  }
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)
  {
   class="kw">return __FUNCTION__;
  }
class="type">void Client::Run(class="type">void)
  {
   Component* component=new ConcreteComponent();
   Decorator* decorator_a=new ConcreteDecoratorA();
   Decorator* decorator_b=new ConcreteDecoratorB();
   decorator_a.component=component;
   decorator_b.component=decorator_a;
   decorator_b.Operation();
   class="kw">delete component;
   class="kw">delete decorator_a;
   class="kw">delete decorator_b;
  }
class="kw">namespace Decorator
{
class Component
  {
class="kw">public:

「装饰器模式在指标组件里的套法」

想把基础指标逻辑和附加过滤层拆开,又不想改原类,装饰器模式在 MT5 里是直接能跑的。下面这段把 Component 作为纯虚基类,ConcreteComponent 是裸逻辑,Decorator 持有 Component 指针并在自身 Operation 里转发调用。 看 Client::Run 的组装顺序:先 new 出 ConcreteComponent,再依次挂 ConcreteDecoratorA 和 ConcreteDecoratorB,后者包前者、前者包裸组件。执行 decorator_b.Operation() 时,调用链会先打 "The concrete operation",再打 added_state 里的 "The added state()",最后由 B 的 AddedBehavior 补一句 "The added behavior()"。 实际验证时把 Run 丢进 EA 的 OnStart,专家日志会按上面三行顺序输出。外汇和贵金属行情跳动快,这种嵌套调用层数多了会增加 tick 内开销,高频场景要自己压一层。 别把指针当自动回收 MQL5 没有垃圾回收,Run 末尾那三行 delete 少写任何一个,组件对象就常驻内存。装饰器套深了更容易漏删,建议封装一个带引用计数的释放函数。

MQL5 / C++
class="kw">virtual class="type">void        Operation(class="type">void)=class="num">0;
  };
class Decorator:class="kw">public Component
  {
class="kw">public:
  Component*        component;
  class="type">void              Operation(class="type">void);
  };
class="type">void Decorator::Operation(class="type">void)
  {
  if(CheckPointer(component)>class="num">0)
    {
      component.Operation();
    }
  }
class ConcreteComponent:class="kw">public Component
  {
class="kw">public:
  class="type">void              Operation(class="type">void);
  };
class="type">void ConcreteComponent::Operation(class="type">void)
  {
  Print("The concrete operation");
  }
class ConcreteDecoratorA:class="kw">public Decorator
  {
class="kw">protected:
  class="type">class="kw">string            added_state;
class="kw">public:
                    ConcreteDecoratorA(class="type">void);
  class="type">void              Operation(class="type">void);
  };
ConcreteDecoratorA::ConcreteDecoratorA(class="type">void):
  added_state("The added state()")
  {
  }
class="type">void ConcreteDecoratorA::Operation(class="type">void)
  {
  Decorator::Operation();
  Print(added_state);
  }
class ConcreteDecoratorB:class="kw">public Decorator
  {
class="kw">public:
  class="type">void              AddedBehavior(class="type">void);
  class="type">void              Operation(class="type">void);
  };
class="type">void ConcreteDecoratorB::AddedBehavior(class="type">void)
  {
  Print("The added behavior()");
  }
class="type">void ConcreteDecoratorB::Operation(class="type">void)
  {
  Decorator::Operation();
  AddedBehavior();
  }
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)
  {
  class="kw">return __FUNCTION__;
  }
class="type">void Client::Run(class="type">void)
  {
  Component* component=new ConcreteComponent();
  Decorator* decorator_a=new ConcreteDecoratorA();
  Decorator* decorator_b=new ConcreteDecoratorB();
  decorator_a.component=component;
  decorator_b.component=decorator_a;
  decorator_b.Operation();
  class="kw">delete component;
  class="kw">delete decorator_a;
  class="kw">delete decorator_b;
  }

用 Facade 把 MT5 子系统调用收口

做 EA 或指标时,订单管理、信号计算、风控校验往往散成好几个类。客户端直接挨个调,耦合重、改一处崩一片。Facade 的思路是:另立一个统一接口类,把子系统的调用细节全兜进去,外层只跟这一个门面打交道。 上面这套 MQL5 示例里,SubSystemA/B/C 各自只管打印自己的操作,彼此没有互相引用;Facade 类持有三个子系统实例,暴露 Operation_A_B 和 Operation_B_C 两个组合方法。Client 只 new 一个 Facade,调两次方法就跑完原本要写六行的逻辑。 实盘接这套结构时,把 SubSystem 换成你真实的 CTrade 封装、ATR 计算、点差过滤模块,Facade 里按交易场景拼装。外汇和贵金属波动大、滑点随机,门面层最好把‘下单前检查’和‘异常回滚’也收进来,降低主逻辑出错概率。 代码贴进 MT5 的 MQ5 文件直接编译,专家日志会依次输出 client 请求、facade 转发、三个子系统各自的 Operation 字样,验证调用链是否如预期分层。

MQL5 / C++
class="kw">namespace Facade
class SubSystemA
  {
class="kw">public:
   class="type">void                Operation(class="type">void);
  };
class="type">void SubSystemA::Operation(class="type">void)
  {
   Print("The operation of the subsystem A");
  }
class SubSystemB
  {
class="kw">public:
   class="type">void                Operation(class="type">void);
  };
class="type">void SubSystemB::Operation(class="type">void)
  {
   Print("The operation of the subsystem B");
  }
class SubSystemC
  {
class="kw">public:
   class="type">void                Operation(class="type">void);
  };
class="type">void SubSystemC::Operation(class="type">void)
  {
   Print("The operation of the subsystem C");
  }
class Facade
  {
class="kw">public:
   class="type">void                Operation_A_B(class="type">void);
   class="type">void                Operation_B_C(class="type">void);
class="kw">protected:
   SubSystemA         subsystem_a;
   SubSystemB         subsystem_b;
   SubSystemC         subsystem_c;
  };
class="type">void Facade::Operation_A_B(class="type">void)
  {
   Print("The facade of the operation of A & B");
   Print("The request of the facade of the subsystem A operation");
   subsystem_a.Operation();
   Print("The request of the facade of the subsystem B operation");
   subsystem_b.Operation();
  }
class="type">void Facade::Operation_B_C(class="type">void)
  {
   Print("The facade of the operation of B & C");
   Print("The request of the facade of the subsystem B operation");
   subsystem_b.Operation();
   Print("The request of the facade of the subsystem C operation");
   subsystem_c.Operation();
  }
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)
  {
   class="kw">return __FUNCTION__;
  }
class="type">void Client::Run(class="type">void)
  {
   Facade facade;
   Print("The request of client of the facade operation A & B");
   facade.Operation_A_B();
   Print("The request of client of the facade operation B & C");
   facade.Operation_B_C();
  }
让小布替你跑这套
这些结构模式的诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到类依赖是否健康,把重复劳动交给小布,你专注决策。

常见问题

创建模式管对象怎么生出来,结构模式管生好的类怎么拼成更大结构;前者解决构造,后者解决组合与接口适配。
可把第三方指标的接口转成你的 EA 期望的格式,不必重写指标逻辑就能直接调用,降低耦合。
可以,小布的内置诊断会提示类间依赖过重或外观层缺失等迹象,帮你定位该用桥接还是代理的地方。
享元通过共享细粒度对象减少内存占用,适合高频 tick 场景下复用行情状态对象,避免重复实例化拖慢执行。
倾向不会,它只是封装了子系统调用;只要保留内部日志,排错反而更清晰,但别把业务判断也塞进外观层。