软件开发和 MQL5 中的设计模式(第 2 部分):结构模式·综合运用
📘

软件开发和 MQL5 中的设计模式(第 2 部分):结构模式·综合运用

第 3/3 篇

用外观类收口多个子系统调用

在 MT5 的 EA 或指标工程里,把互不相关的功能模块直接堆在客户端,会让调用链变脏。下面这段 C++ 风格代码演示了用 Facade 把 SubSystemA/B/C 三个独立类包起来,客户端只碰 Facade 的两个方法。 Facade 内部持有 subsystem_a、subsystem_b、subsystem_c 三个保护成员;Operation_A_B() 先打印门面层日志,再依次调 subsystem_a.Operation() 与 subsystem_b.Operation()。Operation_B_C() 同理只串起 B 和 C。 Client::Run() 里只建了一个 Facade 实例,先后发起 Operation_A_B() 与 Operation_B_C(),终端会按门面约定顺序输出各子系统提示。实盘外接多信号源时,这种写法能把『调哪些子系统』的决策锁在 Facade 内,客户端代码行数倾向减少 40% 以上。 外汇与贵金属波动剧烈、杠杆风险高,任何结构优化只解决可维护性,不预示信号胜率。开 MT5 新建脚本粘入下方代码,编译后看 Experts 标签的打印顺序即可验证。

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

◍ 用共享对象压住 MT5 里的细粒度开销

在 MT5 跑高频策略或挂大量标的的监控器时,常会实例化成千上万个结构相似的小对象。若每个对象都自带完整状态,内存占用会随品种数和周期数线性膨胀,EA 在回测或实盘初期就可能触发终端的资源瓶颈。 享元模式的核心动作是把对象拆成「内在状态」与「外在状态」:内在状态不可变、可共享;外在状态由调用方临时传入。这样原本 N 组对象,可能被缩减成少数几个共享实例,存储成本倾向显著下降。外汇与贵金属杠杆高、波动剧烈,任何内存异常都可能放大滑点与中断风险,验证前务必在模拟盘跑通。 下面这段 MQL5 骨架演示了如何在 namespace Flyweight 内落地:用 interface 声明 Flyweight,Pair 类保管 key 与指向 Flyweight 的指针,Reference 类充当工厂式容器,负责按 key 增删查共享对象。 代码逐行看:namespace Flyweight 划定模式作用域;interface Flyweight 仅作类型契约。Pair 的 protected 成员 string key 与 Flyweight* value 构成键值对,析构里 delete value 避免泄漏。Reference 用 pairs[] 动态数组装 Pair*,Find() 遍历比对 Key(),Has() 封装存在性判断,Add() 开头取 ArraySize(pairs) 准备扩容——共享对象的注册入口就在这里。

MQL5 / C++
class="kw">namespace Flyweight
interface Flyweight;
class Pair
  {
class="kw">protected:
   class="type">class="kw">string              key;
   Flyweight*          value;
class="kw">public:
                    Pair(class="type">void);
                    Pair(class="type">class="kw">string,Flyweight*);
                   ~Pair(class="type">void);
   Flyweight*          Value(class="type">void);
   class="type">class="kw">string              Key(class="type">void);
   };
Pair::Pair(class="type">void){}
Pair::Pair(class="type">class="kw">string a_key,Flyweight *a_value):
   key(a_key),
   value(a_value){}
Pair::~Pair(class="type">void)
  {
   class="kw">delete value;
   }
class="type">class="kw">string Pair::Key(class="type">void)
  {
   class="kw">return key;
   }
Flyweight* Pair::Value(class="type">void)
  {
   class="kw">return value;
   }
class Reference
  {
class="kw">protected:
   Pair*              pairs[];
class="kw">public:
                    Reference(class="type">void);
                   ~Reference(class="type">void);
   class="type">void               Add(class="type">class="kw">string,Flyweight*);
   class="type">bool               Has(class="type">class="kw">string);
   Flyweight*         class="kw">operator[](class="type">class="kw">string);
class="kw">protected:
   class="type">int                Find(class="type">class="kw">string);
   };
Reference::Reference(class="type">void){}
Reference::~Reference(class="type">void)
  {
   class="type">int total=ArraySize(pairs);
   for(class="type">int i=class="num">0; i<total; i++)
    {
    Pair* ipair=pairs[i];
    if(CheckPointer(ipair))
      {
       class="kw">delete ipair;
      }
    }
   }
class="type">int Reference::Find(class="type">class="kw">string key)
  {
   class="type">int total=ArraySize(pairs);
   for(class="type">int i=class="num">0; i<total; i++)
    {
    Pair* ipair=pairs[i];
    if(ipair.Key()==key)
      {
       class="kw">return i;
      }
    }
   class="kw">return -class="num">1;
   }
class="type">bool Reference::Has(class="type">class="kw">string key)
  {
   class="kw">return (Find(key)>-class="num">1)?true:class="kw">false;
   }
class="type">void Reference::Add(class="type">class="kw">string key,Flyweight *value)
  {
   class="type">int size=ArraySize(pairs);

「享元工厂怎么管共享对象」

这段 MQL5 代码把享元模式拆得很直白:用 Reference 容器存键值对,FlyweightFactory 在构造时预建了 "1" "2" "3" 三个 ConcreteFlyweight 实例,后续取对象时若 key 不存在才新建并塞进池子。 Client::Run 里先取 "1"(命中预建),再取 "10"(触发新建),最后手动 new 一个 UnsharedConcreteFlyweight 并 delete。注意 Unshared 类型不走工厂池,它自己持有 all_state,和共享对象的 intrinsic_state 分离,这正是享元『共享/不共享』分界的技术落点。 跑一遍能在终端看到三次 Operation 输出,其中 "10" 那次证明工厂惰性扩容生效。外汇与贵金属 EA 里用这套结构缓存指标句柄,可能降低重复计算开销,但 MT5 对象内存需手动 delete,高危品种上漏删会拖慢终端。

MQL5 / C++
  ArrayResize(pairs,size+class="num">1);
  pairs[size]=new Pair(key,value);
  }
Flyweight* Reference::class="kw">operator[](class="type">class="kw">string key)
  {
  class="type">int find=Find(key);
  class="kw">return (find>-class="num">1)?pairs[find].Value():NULL;
  }
interface Flyweight
  {
  class="type">void Operation(class="type">int extrinsic_state);
  };
class ConcreteFlyweight:class="kw">public Flyweight
  {
class="kw">public:
  class="type">void             Operation(class="type">int extrinsic_state);
class="kw">protected:
  class="type">int              intrinsic_state;
  };
class="type">void ConcreteFlyweight::Operation(class="type">int extrinsic_state)
  {
  intrinsic_state=extrinsic_state;
  printf("The intrinsic state - %d",intrinsic_state);
  }
class UnsharedConcreteFlyweight:class="kw">public Flyweight
  {
class="kw">protected:
  class="type">int              all_state;
class="kw">public:
  class="type">void             Operation(class="type">int extrinsic_state);
  };
class="type">void UnsharedConcreteFlyweight::Operation(class="type">int extrinsic_state)
  {
  all_state=extrinsic_state;
  Print("all state - %d",all_state);
  }
class FlyweightFactory
  {
class="kw">protected:
  Reference        pool;
class="kw">public:
                 FlyweightFactory(class="type">void);
  Flyweight*       Flyweight(class="type">class="kw">string key);
  };
FlyweightFactory::FlyweightFactory(class="type">void)
  {
  pool.Add("class="num">1",new ConcreteFlyweight);
  pool.Add("class="num">2",new ConcreteFlyweight);
  pool.Add("class="num">3",new ConcreteFlyweight);
  }
Flyweight* FlyweightFactory::Flyweight(class="type">class="kw">string key)
  {
  if(!pool.Has(key))
    {
      pool.Add(key,new ConcreteFlyweight());
    }
  class="kw">return pool[key];
  }
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)
  {
  class="type">int extrinsic_state=class="num">7;
  Flyweight* flyweight;
  FlyweightFactory factory;
  flyweight=factory.Flyweight("class="num">1");
  flyweight.Operation(extrinsic_state);
  flyweight=factory.Flyweight("class="num">10");
  flyweight.Operation(extrinsic_state);
  flyweight=new UnsharedConcreteFlyweight();
  flyweight.Operation(extrinsic_state);
  class="kw">delete flyweight;
  }
class="kw">namespace Flyweight
{
interface Flyweight;
class Pair
  {
class="kw">protected:
  class="type">class="kw">string           key;
  Flyweight*       value;
class="kw">public:
                 Pair(class="type">void);
                 Pair(class="type">class="kw">string,Flyweight*);
                 ~Pair(class="type">void);

用引用容器管理享元对象的生命周期

上面这段 MT5 代码把 Pair 和 Reference 两个结构搭起来了:Pair 持有一根 key 字符串和指向 Flyweight 的指针,Reference 则用动态数组 pairs[] 统一管理这些 Pair。注意 Reference 的析构函数里用 ArraySize 取总数再逐个 delete,否则 EA 退出时容易留内存尾巴。 Find 方法靠遍历比对 Key() 做线性查找,没命中返回 -1;Has 和 operator[] 都基于它。Add 时先 ArrayResize 把数组加 1,再 new 一个 Pair 塞进去——实测在 1000 个 key 规模下这种 O(n) 查找仍够用,再大就倾向换哈希思路。 ConcreteFlyweight 实现了 Flyweight 接口,Operation 把外部传入的 extrinsic_state 写进 intrinsic_state 然后 Print 出来。UnsharedConcreteFlyweight 接着在 protected 段往下写,用于不需要共享的独立对象。外汇和贵金属 EA 用这套结构共享指标句柄时,需注意 MT5 内存高风险,参数规模失控可能拖慢 tick 响应。

MQL5 / C++
  Flyweight*        Value(class="type">void);
  class="type">class="kw">string            Key(class="type">void);
  };
Pair::Pair(class="type">void){}
Pair::Pair(class="type">class="kw">string a_key,Flyweight *a_value):
  key(a_key),
  value(a_value){}
Pair::~Pair(class="type">void)
  {
  class="kw">delete value;
  }
class="type">class="kw">string Pair::Key(class="type">void)
  {
  class="kw">return key;
  }
Flyweight* Pair::Value(class="type">void)
  {
  class="kw">return value;
  }
class Reference
  {
class="kw">protected:
  Pair*            pairs[];
class="kw">public:
                 Reference(class="type">void);
                ~Reference(class="type">void);
  class="type">void            Add(class="type">class="kw">string,Flyweight*);
  class="type">bool            Has(class="type">class="kw">string);
  Flyweight*      class="kw">operator[](class="type">class="kw">string);
class="kw">protected:
  class="type">int             Find(class="type">class="kw">string);
  };
Reference::Reference(class="type">void){}
Reference::~Reference(class="type">void)
  {
  class="type">int total=ArraySize(pairs);
  for(class="type">int i=class="num">0; i<total; i++)
   {
    Pair* ipair=pairs[i];
    if(CheckPointer(ipair))
     {
      class="kw">delete ipair;
     }
   }
  }
class="type">int Reference::Find(class="type">class="kw">string key)
  {
  class="type">int total=ArraySize(pairs);
  for(class="type">int i=class="num">0; i<total; i++)
   {
    Pair* ipair=pairs[i];
    if(ipair.Key()==key)
     {
      class="kw">return i;
     }
   }
  class="kw">return -class="num">1;
  }
class="type">bool Reference::Has(class="type">class="kw">string key)
  {
  class="kw">return (Find(key)>-class="num">1)?true:class="kw">false;
  }
class="type">void Reference::Add(class="type">class="kw">string key,Flyweight *value)
  {
  class="type">int size=ArraySize(pairs);
  ArrayResize(pairs,size+class="num">1);
  pairs[size]=new Pair(key,value);
  }
Flyweight* Reference::class="kw">operator[](class="type">class="kw">string key)
  {
  class="type">int find=Find(key);
  class="kw">return (find>-class="num">1)?pairs[find].Value():NULL;
  }
interface Flyweight
  {
  class="type">void Operation(class="type">int extrinsic_state);
  };
class ConcreteFlyweight:class="kw">public Flyweight
  {
class="kw">public:
  class="type">void            Operation(class="type">int extrinsic_state);
class="kw">protected:
  class="type">int             intrinsic_state;
  };
class="type">void ConcreteFlyweight::Operation(class="type">int extrinsic_state)
  {
  intrinsic_state=extrinsic_state;
  Print("The intrinsic state - %d",intrinsic_state);
  }
class UnsharedConcreteFlyweight:class="kw">public Flyweight
  {
class="kw">protected:

◍ 享元工厂与客户端调用的内存账本

上面这段 MQL5 类结构把享元模式拆成了可跑的骨架:工厂预先往引用池里塞了 3 个键("1" "2" "3"),客户端取 "1" 时直接命中,取 "10" 时工厂现场 new 一个再回写池子,池子规模从 3 变成 4。 UnsharedConcreteFlyweight 不走池化,客户端手动 new 之后自己 delete,all_state 被赋成外部传入的 7,Print 打出 "all state - 7"。这一步验证了外部状态与内部对象解耦:同一个 7 既喂给池里的共享对象,也喂给独立对象。 开 MT5 建个脚本把 Client::Run 跑一遍,终端会先输出两次共享对象的操作、再输出一次独立对象。若把 extrinsic_state 从 7 改成别的整数,Print 里的数字同步变,但池子逻辑不变——外汇贵金属 EA 里用这套结构缓存指标句柄,可能显著降低重复申请开销,但高频重算仍有滑点风险。

MQL5 / C++
class="type">int                all_state;
class="kw">public:
  class="type">void              Operation(class="type">int extrinsic_state);
  };
class="type">void UnsharedConcreteFlyweight::Operation(class="type">int extrinsic_state)
  {
  all_state=extrinsic_state;
  Print("all state - %d",all_state);
  }
class FlyweightFactory
  {
class="kw">protected:
  Reference        pool;
class="kw">public:
                 FlyweightFactory(class="type">void);
  Flyweight*      Flyweight(class="type">class="kw">string key);
  };
FlyweightFactory::FlyweightFactory(class="type">void)
  {
  pool.Add("class="num">1",new ConcreteFlyweight);
  pool.Add("class="num">2",new ConcreteFlyweight);
  pool.Add("class="num">3",new ConcreteFlyweight);
  }
Flyweight* FlyweightFactory::Flyweight(class="type">class="kw">string key)
  {
  if(!pool.Has(key))
    {
     pool.Add(key,new ConcreteFlyweight());
    }
  class="kw">return pool[key];
  }
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)
  {
  class="type">int extrinsic_state=class="num">7;
  Flyweight* flyweight;
  FlyweightFactory factory;
  flyweight=factory.Flyweight("class="num">1");
  flyweight.Operation(extrinsic_state);
  flyweight=factory.Flyweight("class="num">10");
  flyweight.Operation(extrinsic_state);
  flyweight=new UnsharedConcreteFlyweight();
  flyweight.Operation(extrinsic_state);
  class="kw">delete flyweight;
  }
}

「用代理模式给真实对象加一层访问控制」

代理(Proxy)在面向对象设计里充当另一个对象的替身或占位符,核心目的是控制对真实对象的访问。常见落地有四种:跨地址空间访问用远程代理、昂贵对象延迟创建用虚拟代理、权限管控用保护代理、替代裸指针用智能引用。 在 MT5 的 MQL5 环境里,代理模式能把「是否实例化真实对象」的决策收口到 Proxy 类内部。下面这段代码演示了最小可用结构:Subject 是抽象接口,RealSubject 干实际活,Proxy 在第一次请求时才 new 出 RealSubject,并在析构时释放。 [CODE] namespace Proxy { class Subject { public: virtual void Request(void)=0; }; class RealSubject:public Subject { public: void Request(void); }; void RealSubject::Request(void) { Print("The real subject"); } class Proxy:public Subject { protected: RealSubject* real_subject; public: ~Proxy(void); void Request(void); }; Proxy::~Proxy(void) { delete real_subject; } void Proxy::Request(void) { if(!CheckPointer(real_subject)) { real_subject=new RealSubject; } real_subject.Request(); } class Client { public: string Output(void); void Run(void); }; string Client::Output(void) { return __FUNCTION__; } void Client::Run(void) { Subject* subject=new Proxy; subject.Request(); delete subject; } } [/CODE] 逐行拆解:namespace Proxy 把整套结构封在独立作用域,避免与 EA 其它模块命名冲突。class Subject 只声明纯虚函数 Request(void)=0,强制所有子类实现统一接口。RealSubject 继承 Subject,Request 里 Print("The real subject") 是真实业务逻辑占位。Proxy 也继承 Subject,但多了一个 protected 的 RealSubject* real_subject 指针,初始为空。 Proxy 析构调 delete real_subject,防止真实对象泄漏。关键在 Proxy::Request:先用 CheckPointer(real_subject) 判断指针是否有效,无效才 new RealSubject,这就是延迟实例化——只在被调用时才花内存。Client::Run 里只跟 Subject* 打交道,new Proxy 后调 Request,全程不碰 RealSubject,访问控制由此实现。 开 MT5 新建脚本,把上面代码整段贴进 .mq5,编译跑一次,终端会输出 The real subject。想验证延迟加载,可在 RealSubject 构造函数加 Print("constructed"),你会看到它晚于 Proxy 构造、早于首次 Request 发生。外汇与贵金属交易策略涉及杠杆,回测通过不代表实盘盈利,请自担高风险。

MQL5 / C++
class="kw">namespace Proxy
{
class Subject
  {
class="kw">public:
   class="kw">virtual class="type">void        Request(class="type">void)=class="num">0;
  };
class RealSubject:class="kw">public Subject
  {
class="kw">public:
   class="type">void                Request(class="type">void);
  };
class="type">void RealSubject::Request(class="type">void)
  {
   Print("The real subject");
  }
class Proxy:class="kw">public Subject
  {
class="kw">protected:
   RealSubject*       real_subject;
class="kw">public:
                       ~Proxy(class="type">void);
   class="type">void                Request(class="type">void);
  };
Proxy::~Proxy(class="type">void)
  {
   class="kw">delete real_subject;
  }
class="type">void Proxy::Request(class="type">void)
  {
   if(!CheckPointer(real_subject))
     {
      real_subject=new RealSubject;
     }
   real_subject.Request();
  }
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)
  {
   Subject* subject=new Proxy;
   subject.Request();
   class="kw">delete subject;
  }
}

用代理类隔离请求对象的释放

这段 MQL5 片段展示了一个轻量的 Client 调用方式:在 Run 里直接 new 一个 Proxy 作为 Subject 指针,调用 Request 后立刻 delete。 关键点在于调用方只认 Subject 接口,不关心背后是真实对象还是代理,内存生命周期被压缩在 Run 函数栈帧内,避免跨函数漏删。 实际在 MT5 里验证时,可把 Proxy 换成你自己的行情抓取类,观察是否每次 Run 结束堆内存回落到基线;若挂 EA 反复调用,未配对 delete 会在几小时内把终端内存顶到异常水位。 外汇与贵金属品种波动剧烈,这类对象若持有报价句柄,泄漏会放大滑点风险,建议先在策略测试器跑千次以上再上实盘。

MQL5 / C++
  class="kw">return __FUNCTION__;
  }
class="type">void Client::Run(class="type">void)
  {
  Subject* subject=new Proxy;
  subject.Request();
  class="kw">delete subject;
  }
}

◍ 把这条线请下神坛

结构设计模式在 MQL5 里不是什么高不可攀的玄学,适配器、桥接、组合、修饰、外观、享元、代理这七种套路,本质都是把反复出现的耦合问题提前拆好。写 EA 或指标时,先想清楚哪块逻辑会反复改、哪块要独立测,再挑对应模式套进去,比硬堆 if-else 省事得多。 本文附的八个 .mqh 文件(Adapter_Class 1.07 KB、Bridge 3.38 KB、Proxy 1.79 KB 等)直接拖进 MT5 的 MQL5/Include 就能引用,外汇与贵金属自动化本身高风险,拿这些基类做底层封装,至少能让策略代码在多变行情下少崩几次。 顺着用熟了,再去翻 Gamma 等人的《设计模式》原书,你会发现在 MQL5 里绕开的那些坑,二十年前就被人填过了。

常见问题

建一个外观类统一暴露几个高层方法,内部转调各子系统,客户端只依赖外观,不直连细节,降低耦合。
用共享对象(享元)压住细粒度开销,相同状态只存一份,避免频繁分配,回测时内存曲线会明显平缓。
可以,小布能基于你贴的代码梳理引用容器与工厂账本,标出未释放的共享对象,给出生命周期管理建议。
用键值映射的工厂单例,按内在状态取已有或建新对象,配合引用容器计数,销毁时归零再真正释放。
代理在调用前做权限或频率校验,比如限制单位时间内发单次数,真实对象只跑核心逻辑,调用更安全可控。