11

我在一个由 25 名开发人员组成的团队中工作。我们使用 Sencha 的 ExtJS MVC 模式。但我们认为他们对 MVC 的定义具有误导性。也许我们也可以称他们的 MVC 为反模式。

AMAIK,在 MVC 控制器中只知道视图的名称或路径,不知道视图的内部结构。例如,视图是否将客户列表呈现为简单的下拉列表或自动完成,这不是控制器的责任。

然而,在 Ext JS 的 MVC 中,控制器应该知道视图元素的渲染,因为控制器会钩住这些元素,并监听它们的事件。这意味着如果视图的一个元素发生了变化(例如一个按钮变成了一个链接),那么控制器中的相关选择器也应该发生变化。换句话说,控制器与视图的内部结构紧密耦合。

这个理由谴责 Ext JS 的 MVC 是反模式可以接受吗?我们认为控制器与视图耦合是对的吗?

4

6 回答 6

19

更新(2015 年 3 月): Ext 5.0 引入了ViewControllers,它应该解决这个线程中讨论的大部分问题。好处:

  • 围绕 ViewController 内的组件引用更好/强制范围
  • 更容易将特定于视图的逻辑与应用程序流控制逻辑分开封装
  • 由框架管理的 ViewController 生命周期以及与之关联的视图

Ext 5 仍然提供现有的Ext.app.Controller类,以保持向后兼容,并为如何构建应用程序提供更大的灵活性。

原答案:

在 Ext JS 的 MVC 中,控制器应该知道视图元素的渲染,因为控制器挂钩到这些元素,并监听它们的事件。这意味着如果视图的一个元素发生了变化(例如一个按钮变成了一个链接),那么控制器中的相关选择器也应该发生变化。换句话说,控制器与视图的内部结构紧密耦合。

我实际上同意,在大多数情况下,由于您引用的确切原因,这不是最佳选择,而且不幸的是,Ext 和 Touch 附带的大多数示例都演示了通常使用违反视图封装的选择器定义的 refs 和控制功能。然而,这不是 MVC 的要求——这只是示例的实现方式,很容易避免。

顺便说一句,我认为区分真正的应用程序控制器(控制应用程序流和共享业务逻辑,应该与视图完全分离——这些就是你所指的)和视图控制器(控制/特定于视图的事件逻辑,通过设计紧密耦合)。后者的示例是在视图内的小部件之间进行协调的逻辑,完全在该视图内部。这是一个常见的用例,将视图控制器耦合到它的视图不是问题——它只是一种代码管理策略,可以使视图类尽可能地哑。

问题在于,在大多数记录的示例中,每个控制器都只是引用它想要的任何内容,这不是一个很好的模式。然而,这不是 Ext 的 MVC 实现的要求——它只是他们的示例中使用的(惰性?)约定。让您的视图类为应该暴露给应用程序控制器的任何内容定义自己的自定义 getter 和事件非常简单(我认为这是可取的)。配置refs只是一个简写——你总是可以像myView.getSomeReference()你自己一样调用一些东西,并允许视图决定返回什么。而不是this.control('some > view > widget')仅仅在视图上定义一个自定义事件并this.control('myevent')在该小部件执行控制器需要知道的事情时执行。就这么简单。

缺点是这种方法需要更多的代码,而且对于简单的情况(如示例),它可能是矫枉过正的。但我同意,对于实际应用程序或任何共享开发,这是一种更好的方法。

所以是的,将应用程序级控制器绑定到内部视图控件本身就是一种反模式。但是Ext的MVC不需要它,避免自己做也很简单。

于 2013-01-24T08:00:48.117 回答
1

我每天都使用 ExtJS 4 的 MVC。我有一个优雅的 MVC 应用程序,而不是意大利面条式代码,它具有严格定义的 concens 分离,并且维护和扩展非常简单。也许您的实现需要稍微调整以充分利用 MVC 方法提供的功能。

于 2013-01-22T12:31:12.747 回答
1

当然,控制器以某种方式绑定到视图。您需要准确定位您想要聆听的视图中的哪些元素。

例如:监听按钮点击或表单元素更改或自定义组件/事件。

MVC 的目标是组件解耦和可重用性,而 Sencha MVC 在这方面很棒。正如@bmoeskau 所说,您必须小心分离视图控制器(视图/小部件本身的内置)和应用程序控制器(顶级视图操作)以充分利用 MVC 模式。当您阅读http://docs.sencha.com/ext-js/4-1/#!/guide/application_architecture时,这并不明显。重构您的 MVC 方法,创建不同的控制器,创建自定义组件,并采用完整的 ExtJS MVC 架构以利用它。

Sencha 方法仍然存在一个小问题,恕我直言,refs当您在应用程序中有多个相同视图的实例时,MVC 系统并不能真正工作。例如:如果您有一个 TabPanel 具有相同组件的多个实例,则 refs 系统将被破坏,因为它总是以 DOM 中找到的第一个元素为目标......有一些变通方法和一个项目试图解决这个问题,但我希望这会尽快得到解决。

于 2013-01-25T02:22:15.023 回答
1

我目前正在参加 Sencha Training 的 ExtJS 4 快速通道。我在 ExtJS(自 ExtJS 2.0 起)方面有很强的背景,并且非常想知道 MVC 是如何在 ExtJS 4 中实现的。

现在,以前,我模拟控制器的方式是将该责任委托给主容器。想象一下 ExtJS 3 中的以下示例:

Ext.ns('Test');

Test.MainPanel = Ext.extend(Ext.Container, {
    initComponent : function() {
        this.panel1 = new Test.Panel1({
            listeners: {
                firstButtonPressed: function(){
                    this.panel2.addSomething();
                },
                scope: this
            }
        });
        this.panel2 = new Test.Panel2();

        this.items = [this.panel1,this.panel2];

        Test.MainPanel.superclass.initComponent.call(this);
    }

});

Test.Panel1 = Ext.extend(Ext.Panel, {
    initComponent : function() {
        this.addEvents('firstButtonPressed');

        this.tbar = new Ext.Toolbar({
           items: [{
             text: 'First Button',
               handler: function(){
                   this.fireEvent('firstButtonPressed');
               }
           }] 
        });

        Text.Panel1.superclass.initComponent.call(this);
    }
});

Test.Panel2 = Ext.extend(Ext.Panel, {
    initComponent : function() {
        this.items = [new Ext.form.Label('test Label')]

        Test.Panel2.superclass.initComponent.call(this);
    },

    addSomething: function(){
        alert('add something reached')
    }
});

如您所见,我的 MainPanel (除了持有两个面板的事实之外)还委托事件并因此在两个组件之间创建通信,因此模拟了某种控制器。

在 ExtJS 4 中直接实现了 MVC。真正让我印象深刻的是控制器实际获取组件的方式是通过 QuerySelector ,在我看来这很容易出错。让我们来看看:

Ext.define('MyApp.controller.Earmarks', {
    extend:'Ext.app.Controller',
    views:['earmark.Chart'],


    init:function () {
        this.control({
             'earmarkchart > toolbar > button':{
                click:this.onChartSelect
            },
            'earmarkchart tool[type=gear]':{
                click:this.selectChart
             }
        });
    }
});

因此,正如我们在此处看到的,Controller 了解专用图表按钮和工具的方式是通过选择器。现在让我们想象一下,我正在更改我的专用图表中的布局,并且我实际上将按钮移到了工具栏之外。突然间我的应用程序坏了,因为我总是需要注意更改布局可能会对与之关联的控制器产生影响。

有人可能会说我可以使用 itemId 来代替,但我需要再次注意,如果我删除了一个组件,我需要分散查找我的控制器中是否有针对该 itemId 的隐藏引用,以及我不能每个父组件具有相同的 itemId,因此如果我在 Panel1 中有一个名为“testId”的 itemId,并且在 Grid1 中有相同的 itemId,那么我仍然需要选择是需要来自 Panel1 还是来自 Grid1 的 itemId。

我知道 Query 非常强大,因为它为您提供了很大的灵活性,但在我看来,这种灵活性的代价非常高,如果我有一个由 5 人组成的团队开发用户界面,我需要解释这个概念,我由于我之前提到的几点,他们会犯很多错误。

您对此有何总体看法?以某种方式与事件进行交流会更容易吗?这意味着如果我的控制器实际上知道他期望事件的视图,那么可以只触发一个事件 dosomethingController 并且关联的控制器会得到它,而不是所有这些查询问题。

于 2014-02-06T11:32:58.657 回答
0

我认为如果您使用 Sencha Architect 生成视图,然后从该视图继承以创建您自己的视图。现在这个 View 可以负责连接任何事件并引发有意义的事件。

这只是一个想法...

//Designer Generated
Ext.define('MyApp.view.MainView', {
    extend: 'Ext.grid.GridPanel',
    alias: 'widget.mainview',    
    initComponent: function() {
    }
});


//Your View Decorator
Ext.define('MyApp.view.MainView', {
    extend: 'MyApp.view.MainViewEx',
    alias: 'widget.mainviewex',    
    initComponent: function() {
    this.mon(this, 'rowselect', function(){
        this.fireEvent('userselected', arguments);
    }, this);
    }
});
于 2014-02-05T03:58:58.237 回答
0

我认为这里有一个非常糟糕的问题——在一个页面内分割隔离单元非常困难。

我正在尝试的方法(这也使得编写测试更容易)是为每个包含逻辑的页面拥有一个 vanilla js 上下文对象(并且具有非常容易分解和委托给不同对象的好处) )。然后,控制器在接收事件时基本上调用上下文对象上的方法,并在它们上具有用于检索视图位或更改视图的方法。

我来自 wpf 背景,喜欢将控制器视为代码隐藏文件。演示者/上下文和视图之间的对话比 wpf 更健谈(因为您没有绑定 + 数据模板),但也不算太糟糕。

还有一个我还没有解决的问题——对控制器使用单例会导致在同一页面上重用 UI 元素的问题。这似乎是一个严重的设计缺陷。我见过的常见解决方案是(再次)将所有内容都堆积到一个文件中,在这种情况下完全放弃控制器。显然,一旦事情开始变得复杂,这不是一个好方法。

似乎从控制器中获取所有状态应该会有所帮助,然后您将拥有第二级上下文对象 - 顶级对象基本上只是为每个视图分配一个唯一的 id 并具有 context=>view 的映射并提供对各个上下文方法的调度——它基本上是一个门面。然后每个视图的状态将在分派到的对象中处理。为什么静态是邪恶的一个很好的例子!

于 2014-07-28T09:42:01.163 回答