Ext JS的MVC是反模式吗?

时间:2013-01-22 10:27:30

标签: model-view-controller extjs anti-patterns

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

AMAIK,MVC控制器只知道视图的名称或路径,并且不了解视图的内部结构。例如,控制器不负责,视图是将客户列表简化为下拉还是自动完成。

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

这个理由是否可以将Ext JS的MVC作为反模式来谴责?控制器是否与视图耦合是否正确?

6 个答案:

答案 0 :(得分:19)

更新(2015年3月): Ext 5.0引入了ViewControllers,应该解决此线程中讨论的大多数问题。优点:

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

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

原始回答:

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

我实际上同意在大多数情况下,根据您引用的确切原因,这不是最佳选择,并且很遗憾Ext和Touch附带的大多数示例都演示了通常使用违反选择器定义的引用和控制函数查看封装。但是,这不是MVC的要求 - 它只是示例的实现方式,而且很容易避免。

顺便说一句,我认为在真正的应用程序控制器(控制应用程序流和共享业务逻辑)之间区分控制器样式肯定是有意义的,应该完全脱离视图 - 这些都是你的意思参考)和查看控制器(特定于视图的控制/事件逻辑,通过设计紧密耦合)。后者的示例是逻辑,用于在视图内部的窗口小部件之间进行协调,完全在内部到该视图。这是一个常见的用例,将视图控制器耦合到其视图不是问题 - 它只是一种代码管理策略,可以使视图类尽可能保持愚蠢。

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

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

所以是的,将app级控制器绑定到内部视图控件本身就是一种反模式。但Ext的MVC并不需要它,而且避免自己动手也很简单。

答案 1 :(得分:1)

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

答案 2 :(得分:1)

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

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

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

Sencha方法恕我直言仍然存在一些问题,当你在一个应用程序中有多个相同视图的实例时,MVC refs系统并没有真正起作用。例如:如果你有一个包含同一组件的多个实例的TabPanel,则refs系统会被破坏,因为它总是以DOM中找到的第一个元素为目标...有变通办法和a project trying to fix that但我希望这将是很快就接受了。

答案 3 :(得分:1)

我目前正在接受Sencha Training的ExtJS 4快速通道。我在ExtJS(从ExtJS 2.0开始)中有很强的背景,并且很想知道如何在ExtJS 4中实现MVC。

现在,以前,我模拟Controller类型的方式是将该责任委托给主容器。想象一下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。真正让我震惊的是,Controller实际获取组件的方式是通过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了解earmarkchart按钮和工具的方式是通过选择器。我们现在想象一下,我正在更改我的earmarkchart中的布局,实际上我将按钮移到工具栏之外。突然之间我的应用程序被破坏了,因为我总是需要注意,更改布局可能会对与之关联的Controller产生影响。

有人可能会说我可以使用itemId,但我需要知道,如果我删除了一个组件,我需要分散以查找我的控制器中是否有任何隐藏的引用用于该itemId,还有事实我不能在每个父组件中拥有相同的itemId,所以如果我在Panel1中有一个名为'testId'的itemId并且在Grid1中有相同的那么我仍然需要选择是否需要来自Panel1的itemId或来自Grid1。 / p>

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

您对此有何总体看法?以某种方式与事件沟通会更容易吗?这意味着如果我的Controller实际上知道他期望事件的视图,那么可以发起事件dosomethingController并且关联的Controller将获得它,而不是所有这个Query问题。

答案 4 :(得分:0)

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

这只是一个想法...

//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);
    }
});

答案 5 :(得分:0)

我认为这里存在一个非常糟糕的问题 - 很难在页面中对孤立的单元进行分片。

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

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

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

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