软件开发和 MQL5 中的设计范式(第一部分):创建范式·进阶篇
🧱

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

(2/3)· 抽象工厂到单例,六种创建型范式在 EA 开发里的落地写法

进阶 第 2/3 篇
写 MQL5 时每加一个信号类就手写一遍初始化逻辑,其实是在反复造轮子。创建型范式把对象生成过程收拢成固定套路,省下的时间够你多回测几个周期。

抽象工厂在 MT5 里的接口与产品落地

想把策略里的指标对象和信号对象解耦,抽象工厂模式在 MQL5 里能直接套。核心是先定义两组纯接口:产品 A、产品 B,以及能同时产出这两类的工厂接口。 下面这段是接口声明骨架,AbstractProductB 里挂了一个 Interact 方法,接收 AbstractProductA 指针,用来表达两类产品之间的协作关系。 namespace AbstractFactory { interface AbstractProductA { }; interface AbstractProductB { void Interact(AbstractProductA*); }; interface AbstractFactory { AbstractProductA* CreateProductA(void); AbstractProductB* CreateProductB(void); }; } 具体产品分两族:ProductA1/A2、ProductB1/B2,各自构造函数里用 Print 打出构建日志。比如 ProductB1::Interact 会输出自身地址和与哪个 A 产品交互,运行时能在专家日志里看到明确轨迹。 工厂实现上,Factory1 的 CreateProductA 返回 new ProductA1,CreateProductB 返回 new ProductB1;Factory2 对应产 A2、B2。切换工厂就等于切换整族产品,不用改调用端代码。 开 MT5 新建一个 mq5 把上面接口和类补完,编译后跑一遍,日志里出现 'Product A1 is constructed' 和 'Factory 1 creates and returns Product B1' 就说明族装配没问题。外汇与贵金属行情高波动,这类结构只解决代码耦合,不预示任何收益。

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);
  };
}

◍ 工厂客户类如何接管具体工厂的切换

Factory2 的构造与两个 Create 方法只是把『2 号工厂造 A2 / B2』打印出来并返回 new 对象,真正的调度逻辑在 FactoryClient。它持有 AbstractFactory* 与两个抽象产品指针,构造时接收工厂并立刻调用 Switch 完成首次装配。 Switch 里先用 StringConcatenate 把旧 factory 转成字符串、再 StringToInteger 转 int 判断:若 iFactory>0 说明之前有工厂,打印切换旧厂到新厂;否则打印接受新厂。随后 Delete 旧资源、赋值新厂、调用 CreateProductA/B 拿新产品。 FactoryClient 的 Run 只做一件事:apb.Interact(apa),即让产品 B 与产品 A 交互;析构与 Delete 统一释放 apa、apb、factory 三个堆对象,避免 MT5 策略退出时内存泄漏。 Client::Run 的实测序列是:先 Print 请求建 Factory1,再建 FactoryClient(new Factory1),最后 client.Run() 触发 A1/B1 交互。在 MT5 里跑这段,专家日志会依次出现『Factory 1 is constructed → accepts new factory → creates Product A1/B1 → runs abstract Product B』,可据此验证工厂模式装配是否按预期。

MQL5 / C++
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();
  }

「用工厂切换演示客户端解耦」

这段 MQL5 片段展示了一个客户端在不修改自身逻辑的前提下,动态切换底层工厂实现并重新运行的过程。对交易者来说,它暗示了 EA 架构里“策略外壳”和“执行内核”可以分离:换工厂等于换下单逻辑,但外层调用不变。 代码里先打印请求创建新工厂 2 的日志,随后用 client.Switch(new Factory2) 把客户端指向 Factory2,再打印重新运行请求并调用 client.Run()。这种写法在需要 A/B 测试不同风控模块时很实用,复制进 MT5 的 EA 模板里就能直接验证切换是否生效。 外汇与贵金属市场杠杆高、滑点随机,这类解耦仅降低代码维护成本,不改善任何盈亏概率,实盘前务必在策略测试器跑足样本。

MQL5 / C++
  Print("The client requests to create the new factory class="num">2 and asks the factory client to class="kw">switch factories");
  client.Switch(new Factory2);
  Print("The client requests the Factory client to run again");
  client.Run();
}

用建造器把复杂对象的拼装和表达拆开

在 MT5 的 EA 或指标工程里,一旦对象由多个部件构成、且同一套构造流程可能产出不同表达,建造器范式就比直接在函数里 new 对象更干净。它把『怎么拼』交给 Director,把『拼出来长啥样』留给 ConcreteBuilder,构造逻辑和内部表达被隔离,后续改表达不必动构造代码。 这套结构里,Builder 是抽象接口,只声明 BuildPartA/B/C 和取回产品的 GetResult;Director 持有 Builder 指针,按固定顺序调三个 BuildPart 完成组装;ConcreteBuilder 才真正往 Product 里塞部件。这样同一次 Construct 流程,换一个 ConcreteBuilder 就能得到另一种产品表达。 下面这段 MQL5 模块可直接贴进 MetaEditor 验证:Product 用动态数组存部件,Add 里 ArrayResize 每次 +1,意味着每加一个部件数组长度递增 1,部件数等于调用 Add 的次数。Director 的析构里 delete builder,提醒你 Builder 实例必须走堆分配,栈对象会被重复释放。 别把 Director 写成万能组装机 Director::Construct 目前硬编码了 A→B→C 的顺序,若你的实盘信号需要按波动率动态决定部件顺序,应把构造步骤外放到 Client,而不是塞进 Director,否则建造器就退化成普通工厂了。

MQL5 / C++
class="kw">namespace Builder
class Product
  {
class="kw">public:
   class="type">void                Add(class="type">class="kw">string);
   class="type">void                Show();
class="kw">protected:
   class="type">class="kw">string              parts[];
  };
class="type">void Product::Add(class="type">class="kw">string part)
  {
   class="type">int size=ArraySize(parts);
   ArrayResize(parts,size+class="num">1);
   parts[size]=part;
   Print("The product added ",part," to itself");
  }
class="type">void Product::Add(class="type">class="kw">string part)
  {
   class="type">int size=ArraySize(parts);
   ArrayResize(parts,size+class="num">1);
   parts[size]=part;
   Print("The product added ",part," to itself");
  }
interface Builder
  {
   class="type">void BuildPartA();
   class="type">void BuildPartB();
   class="type">void BuildPartC();
   Product* GetResult();
  };
class Director
  {
class="kw">public:
   class="type">void                Construct();
                      Director(Builder*);
                     ~Director();
class="kw">protected:
   Builder*            builder;
  };
class="type">void Director::Director(Builder *b)
  {
   builder=b;
   Print("The director created and received the builder ",b);
  }
class="type">void Director::~Director(class="type">void)
  {
   class="kw">delete builder;
  }
class="type">void Director::Construct(class="type">void)
  {
   Print("The director started the construction");
   Print("The director requestd its builder to build the product parts");
   builder.BuildPartA();
   builder.BuildPartB();
   builder.BuildPartC();
   Print("The director&class="macro">#x27;s builder constructed the product from parts");
  }
class ConcreteBuilder:class="kw">public Builder
  {
class="kw">public:
   class="type">void                BuildPartA();
   class="type">void                BuildPartB();
   class="type">void                BuildPartC();
   Product*            GetResult();
class="kw">protected:
   Product             product;
  };
class="type">void ConcreteBuilder::BuildPartA(class="type">void)
  {
   Print("The builder requests the product to add part A to itself");
   product.Add("part a");
   Print("The builder made the part of A and added it to the product");
  }
class="type">void ConcreteBuilder::BuildPartB(class="type">void)
  {
   Print("The builder requests the product to add part B to itself");

◍ 构造器如何拼装产品对象

在 MT5 的 MQL5 环境里,用建造者模式拼装复杂对象时,核心动作落在 ConcreteBuilder 的 BuildPartA/B/C 与 GetResult 上。每调用一次 BuildPart 方法,就往内部 product 里塞一个字符串部件,并伴随 Print 日志输出,方便在专家日志里追踪装配顺序。 Product 类用动态数组 parts[] 存部件,Add 方法先取 ArraySize 得到当前长度,再 ArrayResize 扩 1,把新部件写进末位。Show 方法则循环打印全部部件——这段逻辑跑通后,日志里会依次出现 part a、part b、part c 三个字符串。 Client::Run 是触发入口:先 new 一个 ConcreteBuilder,交给 Director,调 Construct 让它按顺序调三个 BuildPart,最后 GetResult 拿回产品并 Show。外汇与贵金属 EA 开发中套用这套结构,能把参数组装和策略初始化解耦,但杠杆品种波动剧烈,回测通过不代表实盘概率占优。 下面这段是原文里 Product 与 Builder 接口的关键代码,逐行看清楚谁持有数据、谁只定协议。

MQL5 / C++
class="kw">namespace Builder
{
class Product
  {
class="kw">public:
  class="type">void                Add(class="type">class="kw">string);
  class="type">void                Show();
class="kw">protected:
  class="type">class="kw">string              parts[];
  };
class="type">void Product::Add(class="type">class="kw">string part)
  {
  class="type">int size=ArraySize(parts);
  ArrayResize(parts,size+class="num">1);
  parts[size]=part;
  Print("The product added ",part," to itself");
  }
class="type">void Product::Show(class="type">void)
  {
  Print("The product shows all parts that it is made of");
  class="type">int total=ArraySize(parts);
  for(class="type">int i=class="num">0; i<total; i++)
      Print(parts[i]);
  }
interface Builder
  {
  class="type">void BuildPartA();
  class="type">void BuildPartB();
  class="type">void BuildPartC();
  Product* GetResult();
  };
}

「把构建逻辑拆给 Director 与 Builder 分工」

上面这段 C++ 风格代码演示了建造者模式在 MT5 自定义类里的落地:ConcreteBuilder 负责把 part a / b / c 逐个塞进 product,Client 只管发指令,不碰组装细节。 BuildPartA 到 BuildPartC 每个方法都先 Print 一句请求、再 product.Add("part x")、最后 Print 确认添加完成;GetResult 则返回 product 地址。 Client::Run 里先 new 一个 ConcreteBuilder,再把它交给 Director,调用 director.Construct() 触发完整构建,最后 product.Show() 打印成果。开 MT5 写个 EA 把这段类结构抄进去,专家日志会按 a→b→c 顺序输出 6 条 builder 提示加 1 条 director 提示,共 7 次 Print 调用。 外汇与贵金属杠杆高,这类封装只解决代码组织,不暗示任何信号胜率,回测前先想清楚构建出的对象是否真能映射你的订单管理逻辑。

MQL5 / C++
class="type">void                BuildPartA();
class="type">void                BuildPartB();
class="type">void                BuildPartC();
Product*            GetResult();
class="kw">protected:
  Product           product;
};
class="type">void ConcreteBuilder::BuildPartA(class="type">void)
  {
  Print("The builder requests the product to add part A to itself");
  product.Add("part a");
  Print("The builder made the part of A and added it to the product");
  }
class="type">void ConcreteBuilder::BuildPartB(class="type">void)
  {
  Print("The builder requests the product to add part B to itself");
  product.Add("part b");
  Print("The builder made the part of B and added it to the product");
  }
class="type">void ConcreteBuilder::BuildPartC(class="type">void)
  {
  Print("The builder requests the product to add part C to itself");
  product.Add("part c");
  Print("The builder made part C and added it to the product");
  }
Product* ConcreteBuilder::GetResult(class="type">void)
  {
  Print("The builder is returns the product");
  class="kw">return &product;
  }
class Client
  {
class="kw">public:
  class="type">class="kw">string            Output();
  class="type">void              Run();
  };
class="type">class="kw">string Client::Output() {class="kw">return __FUNCTION__;}
class="type">void Client::Run()
  {
  Print("The client requests to create a new concrete builder");
  Builder* builder=new ConcreteBuilder;
  Print("The client requests to create a director and give him the builder");
  Director director(builder);
  Print("The client requests the director to perform the construction");
  director.Construct();
  Print("The client requests the builder to class="kw">return the result product");
  Product* product=builder.GetResult();
  Print("The client is requests the product to describe itself");
  product.Show();
  }
把样板代码交给小布
小布盯盘的 AIGC 已内置常见创建型范式片段,打开对应品种页即可直接套用工厂方法生成订单管理对象,你只管调参数。

常见问题

抽象工厂产出一组相关对象,工厂方法只负责单个产品派生;EA 里多品种多周期管理器适合前者,单一指标切换适合后者。
可以,内置模块能按你选的范式输出基础结构,但业务规则仍需你补,外汇贵金属波动高风险需自担。
若用静态指针且未在 OnDeinit 释放,可能因测试代理复用导致状态残留,倾向在初始化时显式置空。
概率上适合,浅拷贝注意指针成员;深拷贝开销随 K 线量上升,可能拖慢 tick 处理。