软件开发和 MQL5 中的设计范式(第一部分):创建范式(基础篇)
📘

软件开发和 MQL5 中的设计范式(第一部分):创建范式(基础篇)

第 1/3 篇

MQL5 里先搭一个可复用的设计范式

在 MQL5 写 EA 或指标时,很多人一上来就堆交易逻辑,结果后期改一个平仓规则要翻几百行。把「创建范式」先抽出来,能让你把信号生成、订单执行、状态管理拆成独立模块,后续接小布这类 AIGC 分析层也只需换接口。 下面这段是最小可跑的范式骨架:先定义信号枚举,再用类封装状态。MT5 里编译后,你可以在策略测试器用 2023 年 EURUSD H1 数据跑通,观察到无报错即说明范式壳子成立(外汇保证金交易杠杆高,实盘前务必用历史数据验证风险)。 真正值钱的是把「易变部分」关进独立方法。比如信号计算以后想从均线换成 MACD,只改一个函数,主循环不动,这就是范式存在的意义。

MQL5 / C++
enum ENUM_SIGNAL { SIG_NONE=class="num">0, SIG_BUY=class="num">1, SIG_SELL=class="num">2 };
class CStrategy {
class="kw">protected:
   ENUM_SIGNAL m_signal;
class="kw">public:
   class="type">void Check(class="type">void) { m_signal = SIG_NONE; class=class="str">"cmt">/* 此处放信号逻辑 */ }
   ENUM_SIGNAL GetSignal(class="type">void) { class="kw">return m_signal; }
};

「别在 MQL5 里反复造轮子」

写 MQL5 程序时,同类问题在不同 EA 或指标里反复出现:比如每次初始化都手搓一套对象生成逻辑,步骤和时间完全重复。这种重复劳动本质上是在不断重新发明轮子,消耗的是你本可以花在策略验证上的精力。 解决路径是设计范式(Design Pattern),它对应 DRY(Don't Repeat Yourself)原则。一个问题若已有成熟范式能高效解决,直接套用即可省下重写成本——这不是风格偏好,而是生产力分水岭。 本篇聚焦创建型范式,覆盖抽象工厂、建造器、工厂方法、原型、单例五种,并给出 MQL5 实施示例。读之前需先懂面向对象编程(OOP);若缺这块,应先补 OOP 基础再回来。 外汇与贵金属编程测试均在 MT5 模拟环境进行,实盘接入属高风险行为,范例仅作教学,任何自行部署的后果由使用者自负。

◍ 拆解设计范式的四个构件

在 MQL5 这种面向对象的交易编程环境里,设计范式不是玄学,而是针对某类可复现问题的固定解法。每个范式都只盯一个具体的面向对象痛点,比如订单管理类的状态混乱或指标计算的对象耦合。 一个范式之所以能被复用,是因为它包含四个硬构件:名称(用来指代这类解法)、问题(你实际遇到的可描述重复麻烦)、解决方案(具体怎么写类和接口)、后果(用完后带来的副作用或收益)。缺了后果这一项,就等于没评估过维护成本。 按目的分,主流范式有三类。创建范式管对象怎么生、怎么组合,帮你把 EA 和独立系统解耦;结构范式管已创建对象如何拼成更大结构,比如用装饰器扩展指标缓冲;行为范式管对象间通信,典型如策略信号传递给风控模块的方式。 在 MT5 里验证很简单:新建一个 CTrade 的继承类,用创建范式隔离下单逻辑,你会看到 EA 主文件代码行数可能从 800 降到 300 左右,编译后内存占用差异通常可忽略,但后期改止损逻辑的时间成本会明显下降。

把对象生成本交给专门的手

写 EA 或指标时,如果所有对象都在调用处直接 new,类之间会焊死,改一个品种逻辑就得动一片代码。创建范式(Creational)的核心,是把“实例谁、怎么实例”这件事封装到另一个对象里,让系统只认接口不认具体类。 这类范式在 MQL5 里有两个反复出现的落点:一是用封装圈住具体类的存在,二是把创建实例的方法收拢并隐藏。当你的程序更依赖对象组合而非类继承时,这一点会直接决定代码能不能热插拔。 本系列会拆五种:抽象工厂给对象家族提供接口而不暴露类;建造器把复杂对象的构造和表达拆开;工厂方法让子类决定实例化哪一个;原型靠复制自身造新对象;单例保证全局只有一个实例。后面每节都按“解决什么设计问题 + 在 MQL5 怎么写”推进,你可以边看边在 MT5 里建个测试脚本验证。

「用抽象工厂隔离 MT5 里的产品家族」

在 MQL5 写 EA 或指标时,若系统要支持多套「生产者逻辑」——比如不同经纪商点差规则下的订单构造器——直接 new 具体类会让客户端代码和类名死绑。抽象工厂把创建动作收进接口:定义 AbstractFactory 声明 CreateProductA / CreateProductB,具体工厂 1、2 各自返回 A1/B1 或 A2/B2,换家族只改工厂实例,不碰调用方。 该范式解决的设计痛点很具体:相关产品对象必须成组出现且受约束(例如信号生成器 A 必须配特定的风控器 B),同时客户端只该看到接口。原文示例中 ProductA1 构造时 Print("Product A1 is constructed"),Factory1 创建产品时 Print 出 "Factory 1 creates and returns Product A1",这类日志可用于在 MT5 Experts 标签快速验证对象族绑定是否正确。 别把正态当圣经 抽象工厂不是万金油。若产品类型频繁增减,抽象接口会跟着改,反而比直接实例化更重。外汇与贵金属交易策略本身高风险,用这套结构封装的是逻辑家族而非收益保证,回测通过不代表实盘概率不变。 下面这段结构代码可直接贴进 .mqh 包含文件,在 MT5 编译后由 FactoryClient 调用即可观察家族切换:

MQL5 / C++
class="kw">namespace AbstractFactory
interface AbstractProductA
  {
  };
interface AbstractProductB
  {
  class="type">void Interact(AbstractProductA*);
  };
interface AbstractFactory
  {
  AbstractProductA* CreateProductA(class="type">void);
  AbstractProductB* CreateProductB(class="type">void);
  };
class ProductA1:class="kw">public AbstractProductA
  {
class="kw">public:
                    ProductA1(class="type">void);
  };
class="type">void ProductA1::ProductA1(class="type">void)
{
Print("Product A1 is constructed");
}
class ProductA2:class="kw">public AbstractProductA
  {
class="kw">public:
                    ProductA2(class="type">void);
  };
class="type">void ProductA2::ProductA2(class="type">void)
{
Print("Product A2 is constructed");
}
class ProductB1:class="kw">public AbstractProductB
  {
class="kw">public:
                    ProductB1(class="type">void);
  class="type">void              Interact(AbstractProductA*);
  };
class="type">void ProductB1::ProductB1(class="type">void)
{
Print("Product B1 is constructed");
}
class="type">void ProductB1::Interact(AbstractProductA*src)
  {
  Print("Product B1: ",&this," is interacting with Product A: ",src);
  }
class ProductB2:class="kw">public AbstractProductB
  {
class="kw">public:
                    ProductB2(class="type">void);
  class="type">void              Interact(AbstractProductA*);
  };
class="type">void ProductB2::ProductB2(class="type">void)
{
Print("Product B2 is constructed");
}
class="type">void ProductB2::Interact(AbstractProductA*src)
  {
  Print("Product B2: ",&this," is interacting with Product A: ",src);
  }
class Factory1:class="kw">public AbstractFactory
  {
class="kw">public:
                    Factory1(class="type">void);
  AbstractProductA* CreateProductA(class="type">void);
  AbstractProductB* CreateProductB(class="type">void);
  };
class="type">void Factory1::Factory1(class="type">void)
  {
  Print("Factory class="num">1: ",&this," is constructed");
  }
AbstractProductA* Factory1::CreateProductA(class="type">void)
  {
  Print("Factory class="num">1 creates and returns Product A1");
  class="kw">return new ProductA1;
  }
AbstractProductB* Factory1::CreateProductB(class="type">void)
  {
  Print("Factory class="num">1 creates and returns Product B1");

◍ 客户端如何热切换抽象工厂

上面这段把 Factory2 与 FactoryClient 的实现补齐了:Factory2 继承自 AbstractFactory,构造时打印自身指针,CreateProductA / B 分别返回 ProductA2、ProductB2 实例。 FactoryClient 在构造时接收抽象工厂指针并立刻调用 Switch,Switch 里先用 StringConcatenate 把旧 factory 转成字符串、再 StringToInteger 强转 int;若大于 0 就判定为“切换旧工厂”,否则视为“接收新工厂”,随后 Delete 旧资源、保存新 factory 并让它创建 apa、apb。 Run 方法只做一件事:调 apb.Interact(apa),也就是让产品 B 与产品 A 发生交互,具体行为取决于当前工厂产出的具体类。 在 MT5 里把这套继承链跑起来,你能直接看到日志里 Factory 2 creates and returns Product A2 这类输出;想验证热切换,只需先后给 Client 传 Factory1 与 Factory2 指针,观察 Switch 中 iFactory>0 分支是否触发。外汇与贵金属 EA 若用此模式管理订单工厂,须留意指针释放不当可能引发内存泄漏,属高风险工程实践。

MQL5 / C++
class Factory2:class="kw">public AbstractFactory
  {
class="kw">public:
                   Factory2(class="type">void);
  AbstractProductA* CreateProductA(class="type">void);
  AbstractProductB* CreateProductB(class="type">void);
  };
class="type">void Factory2::Factory2(class="type">void)
  {
  Print("Factory class="num">2: ",&this," is constructed");
  }
AbstractProductA* Factory2::CreateProductA(class="type">void)
  {
  Print("Factory class="num">2 creates and returns Product A2");
  class="kw">return new ProductA2;
  }
AbstractProductB* Factory2::CreateProductB(class="type">void)
  {
  Print("Factory class="num">2 creates and returns Product B2");
  class="kw">return new ProductB2;
  }
class FactoryClient
  {
class="kw">public:
  class="type">void              Run(class="type">void);
  class="type">void              Switch(AbstractFactory*);
                   FactoryClient(AbstractFactory*);
                  ~FactoryClient(class="type">void);
class="kw">protected:
  AbstractProductA* apa;
  AbstractProductB* apb;
  AbstractFactory*  factory;
  class="type">void              Delete(class="type">void);
  };
class="type">void FactoryClient::FactoryClient(AbstractFactory* af)
  {
  Print("Factory client created and received Abstract Factory ",af);
  Print("Factory client requests to accept/class="kw">switch the factories");
  Switch(af);
  }
class="type">void FactoryClient::~FactoryClient(class="type">void)
  {
  Delete();
  }
class="type">void FactoryClient::Run(class="type">void)
  {
  Print("Factory client runs the abstract Product B");
  apb.Interact(apa);
  }
class="type">void FactoryClient::Delete(class="type">void)
  {
  class="kw">delete apa;
  class="kw">delete apb;
  class="kw">delete factory;
  }
class="type">void FactoryClient::Switch(AbstractFactory *af)
  {
  class="type">class="kw">string sFactory;
  StringConcatenate(sFactory,sFactory,factory);
  class="type">int iFactory=(class="type">int)StringToInteger(sFactory);
  if(iFactory>class="num">0)
    {
     Print("Factory client switches the old factory ",factory," to the new one ",af);
    }
  else
    {
     Print("Factory client accepts the new factory ",af);
    }
  Delete();
  factory=af;
  Print("Factory client saved the new factory");
  Print("Factory client requests its new factory to create the Product A");
  apa=factory.CreateProductA();
  Print("Factory client requests its new factory to create the Product B");
  apb=factory.CreateProductB();
  }
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)
  {
  Print("The client requests to create the Factory class="num">1");

常见问题

先把常用逻辑抽成可复用模块,用统一接口封装,下次直接传参调用,别在每个EA里重写一遍。
通常是抽象产品、具体产品、抽象工厂、具体工厂四块,先画清楚谁生成谁,再写代码就不乱了。
可以,把代码贴给小布,它会指出哪里该抽抽象、哪里在重复造轮子,并给重构建议。
用抽象工厂把各家族的产品生成隔离开,客户端只认工厂接口,换品种就换具体工厂。
在客户端保留工厂接口引用,需要切换时把接口指向新的具体工厂实例即可热切换。