MVC 设计范式及其应用(第 2 部分):三个组件之间相互作用示意图·综合运用
(3/3)· 当模型、视图、控制器真正连起来跑,多数 MQL5 项目才暴露出耦合隐患
接上篇,我们继续深挖三个组件之间如何实际相互作用。若交互机制研究不充分或不准确,使用该模式带来的可维护性优势就可能被削弱,这也是前文评论区读者指出的关键缺口。
「给指标加个按钮切换线图和柱状图」
想在 MT5 子窗口里把指标从直线动态改成柱状图,最省事的做法是放一个图形按钮,点击即切换。我们用 CButtonObj 封装按钮的创建、存储和删除,控制器直接接管这个用户交互元素,不在主逻辑里散落对象操作。 主文件只需挂一个 OnChartEvent,把图表事件甩给控制器;视图层加一个 VIEW_TYPE 枚举(LINE / HISTO)和切换方法。这样即便一年后回来改样式,顺着控制器→视图的结构也能几分钟内定位,不依赖文档记忆。 下面这段是核心切换逻辑:按钮名匹配后,根据当前视图类型在 LINE 与 HISTO 之间翻转,SetNewViewType 里先判等再改成员并触发 SwitchViewType,后者用 PlotIndexSetInteger 改 PLOT_DRAW_TYPE 后 ChartRedraw 重绘。外汇与贵金属波动剧烈,这类交互改样只影响视觉呈现,不改变信号逻辑,实盘前请在模拟盘验证。
class="type">void OnChartEvent(const class="type">int id, const class="type">long &lparam, const class="type">class="kw">double &dparam, const class="type">class="kw">string &sparam) { pController.ChartEvent(id, lparam, dparam, sparam); } enum VIEW_TYPE { LINE, HISTO }; class CView : class="kw">public IOutputBase { class="kw">private: ... VIEW_TYPE view_type; class="kw">protected: class="type">void SwitchViewType(); class="kw">public: CView() {view_type = LINE;} ... const VIEW_TYPE GetViewType() const {class="kw">return view_type;} class="type">void SetNewViewType(VIEW_TYPE vt); }; class="type">void CView::SetNewViewType(VIEW_TYPE vt) { if (view_type == vt) class="kw">return; view_type = vt; SwitchViewType(); } class="type">void CView::SwitchViewType() { class="kw">switch (view_type) { case LINE: PlotIndexSetInteger(class="num">0, PLOT_DRAW_TYPE, DRAW_LINE ); break; case HISTO: PlotIndexSetInteger(class="num">0, PLOT_DRAW_TYPE, DRAW_HISTOGRAM ); break; } ChartRedraw(); } class="type">void CController::ChartEvent(const class="type">int id,const class="type">long &lparam,const class="type">class="kw">double &dparam,const class="type">class="kw">string &sparam) { class="kw">switch (id) { case CHARTEVENT_OBJECT_CLICK: if (StringCompare(sparam, pBtn.GetName()) == class="num">0) { if (pView.GetViewType() == LINE) pView.SetNewViewType(HISTO); else pView.SetNewViewType(LINE); } break; class="kw">default: break; }class=class="str">"cmt">//class="kw">switch (id) }
控制器才是真正的依赖中心
从文件开头的 #include 集合就能看出,控制器几乎牵动了所有其它模块——每包含一个文件,就引入一条依赖。形式上说这没错,但控制器的本职只是创建模块、管生命周期、把事件转发出去,它更像一台提供动力并对接用户的引擎。 所有组件都暴露了 Initialize、Update、Release 这组近似的方法。再往下抽一层,用一组虚方法做基类会更干净;Initialize 各组件内部逻辑不同,但仍能找到共用解法。 一个更诱人但更麻烦的思路,是让 CInputManager 返回指向接口的指针,这样独立组件只能碰到受限的输入参数集合。本文暂未落地这点。之所以反复围着输入参数模块打转,是想示范后续模块的搭建路径——比如 CView 不必像现在这样靠层次关系实现 IOutputBase,它完全能选别的组合方式。 外汇与贵金属 EA 架构高风险:模块耦合若失控,实盘中断概率显著上升,改动前建议在 MT5 策略测试器先做隔离验证。
class CInputManager { ... class="kw">public: InputBase* GetInput(); ... };
◍ 把工具请下神坛
三篇写下来,MVC 在 MQL5 里从概念落到组件互动,WPR_MVC.zip(18.77 KB)就是那套可拆可改的实物。它不是一个必须跪拜的架构,只是把指标、视图、控制器拆开之后,你改一块不至于崩全盘。 评论区里 Daniil 提了几点很实在:输入参数用 uint 挡掉负值、别偷偷重设默认值掩盖用户错误、用字典存数据、少造轮子多用标准类、虚方法留好继承口子。这些都不是教条,是跑过策略测试器之后的血泪经验。 外汇和贵金属杠杆高、滑点狠,任何范式都不能替你扛回撤。开 MT5 把 zip 解了,先只读 CIndicator 那层,改个缓冲名看图表反应,比背架构图有用。