软件开发和 MQL5 中的设计模式(第 2 部分):结构模式·进阶篇
(2/3)·适配器到外观六模式落地 MT5,避免 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 派生类一个写文件、一个推小布告警,抽象层完全不用动。外汇与贵金属波动剧烈,这类结构只解决代码组织,不预示任何收益。
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 用,不报错就会静默丢失信号分支,排查起来极费时。
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 策略测试器里会留下内存泄漏告警,建议自己补析构或手动释放验证。
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()。外汇与贵金属交易策略开发高风险,挂负载逻辑前务必在策略测试器跑多周期验证。
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 少写任何一个,组件对象就常驻内存。装饰器套深了更容易漏删,建议封装一个带引用计数的释放函数。
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 字样,验证调用链是否如预期分层。
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(); }