MVC 设计范式及其应用(第 2 部分):三个组件之间相互作用示意图·综合运用
🔗

MVC 设计范式及其应用(第 2 部分):三个组件之间相互作用示意图·综合运用

(3/3)· 当模型、视图、控制器真正连起来跑,多数 MQL5 项目才暴露出耦合隐患

进阶 第 3/3 篇

接上篇,我们继续深挖三个组件之间如何实际相互作用。若交互机制研究不充分或不准确,使用该模式带来的可维护性优势就可能被削弱,这也是前文评论区读者指出的关键缺口。

「给指标加个按钮切换线图和柱状图」

想在 MT5 子窗口里把指标从直线动态改成柱状图,最省事的做法是放一个图形按钮,点击即切换。我们用 CButtonObj 封装按钮的创建、存储和删除,控制器直接接管这个用户交互元素,不在主逻辑里散落对象操作。 主文件只需挂一个 OnChartEvent,把图表事件甩给控制器;视图层加一个 VIEW_TYPE 枚举(LINE / HISTO)和切换方法。这样即便一年后回来改样式,顺着控制器→视图的结构也能几分钟内定位,不依赖文档记忆。 下面这段是核心切换逻辑:按钮名匹配后,根据当前视图类型在 LINE 与 HISTO 之间翻转,SetNewViewType 里先判等再改成员并触发 SwitchViewType,后者用 PlotIndexSetInteger 改 PLOT_DRAW_TYPE 后 ChartRedraw 重绘。外汇与贵金属波动剧烈,这类交互改样只影响视觉呈现,不改变信号逻辑,实盘前请在模拟盘验证。

MQL5 / C++
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 策略测试器先做隔离验证。

MQL5 / C++
class CInputManager {
  ...
 class="kw">public:
  InputBase*  GetInput();
  ...
};

◍ 把工具请下神坛

三篇写下来,MVC 在 MQL5 里从概念落到组件互动,WPR_MVC.zip(18.77 KB)就是那套可拆可改的实物。它不是一个必须跪拜的架构,只是把指标、视图、控制器拆开之后,你改一块不至于崩全盘。 评论区里 Daniil 提了几点很实在:输入参数用 uint 挡掉负值、别偷偷重设默认值掩盖用户错误、用字典存数据、少造轮子多用标准类、虚方法留好继承口子。这些都不是教条,是跑过策略测试器之后的血泪经验。 外汇和贵金属杠杆高、滑点狠,任何范式都不能替你扛回撤。开 MT5 把 zip 解了,先只读 CIndicator 那层,改个缓冲名看图表反应,比背架构图有用。

把重复劳动交给小布
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到组件调用耗时与异常预警,你只管优化架构。

常见问题

常见做法是将无效输入回退为默认值,例如周期参数误设为负时重置为 14,再继续转换逻辑,避免 OnInit 堆积脏数据。
目前小布盯盘不解析源码分层,但可监控指标输出与警报触发,间接反映视图与模型衔接是否通畅,辅助你定位断层。
模型若感知视图存在,就破坏了职责隔离;一旦界面改动,模型也需重编,MVC 的可替换性基本失效。
注释应紧贴组件边界说明数据流向,而非逐行解释语法;收尾篇的示例代码展示了轻量标注如何降低后续接手成本。