如何在MVC架构中最好地公开Java组件,例如JButton?

3
我正在使用Java编写一个Blackjack应用程序,需要采用MVC架构。我已经完全编码了所有模型、视图和控制器,并且一切都运行得很完美。然而,我想知道将监听器附加到组件(例如JButton)的最佳方法是什么。
我的当前设计将所有需要侦听器的视图组件声明为public final。然后将视图传递给控制器。从那里,控制器可以完全访问组件并添加操作侦听器。代码示例:
public class View extends JFrame {
    public final JButton myBtn1 = new JButton("Button 1");
    public final JButton myBtn2 = new JButton("Button 2");

    public View() {
        //constructor code here....
    }
}

public class Controller {
    private Model myModel;
    private View myView;

    public Controller(Model m, View v) {
        myModel = m;
        myView = v;

        //now add listeners to components...

        myView.myBtn1.addActionListener(new MyActionListener1());
        myView.myBtn2.addActionListener(new MyActionListener2());
    }
}

然而,我的一个朋友将他所有的JButton声明为私有作用域,然后在视图中创建公共方法来为它们各自的组件添加动作监听器。对我来说,这似乎只是浪费时间,增加了不必要的代码。代码示例:

public class View extends JFrame {
    private JButton myBtn1 = new JButton("Button 1");
    private JButton myBtn2 = new JButton("Button 2");

    public View() {
        //constructor code here....
    }

    public void myBtn1AddActionListener(ActionListener a) {
        myBtn1.addActionListener(a);
    }

    public void myBtn2AddActionListener(ActionListener a) {
        myBtn2.addActionListener(a);
    }

    //etc etc...
}

public class Controller {
    private Model myModel;
    private View myView;

    public Controller(Model m, View v) {
        myModel = m;
        myView = v;

        //now add listeners to components...

        myView.myBtn1AddActionListener(new MyActionListener1());
        myView.myBtn2AddActionListener(new MyActionListener2());
    }
}

因此,根据以上两种情况,哪一种方式更好呢?
谢谢。

1
“需要使用MVC架构编写”?如果这是一道作业/课程作业问题,那么在此提问是可以的,但请标记为作业,以便人们知道要给你建议/让你思考,而不是替你完成它 ;) - ChrisW
1
哈哈,好吧,我并不是让别人替我做这件事 - 就像我说的,它完全正常工作。这只是一个关于“最佳实践”的问题... :-) - tjramage
4个回答

5
我的当前设计将视图中需要监听器的所有组件声明为public final。然后,它将视图传递给控制器。从那里,控制器可以完全访问组件并添加动作侦听器。代码示例:
不要毫无必要地暴露字段--您不应该因MVC而牺牲封装性。您不应该出于上述原因使组件公开。相反,给视图公共方法,由控制器调用,或者通过侦听器使视图更改模型状态。将控件传递给视图和视图传递给控件,所有交互都应通过公共方法进行,而不是通过公开组件或字段来进行。
我解决这个问题的一种方法,并不是说这是最好或规范的方法,是将Control的一个实例传递到View中,并使用匿名内部类进行我的侦听器,并让这些类调用控制器方法。例如:
// inside of view
exitButton.addActionListener(new ActionListener() {
   public void actionPerformed(ActionEvent evt) {
      if (control != null) {
         control.exitAction();
      }
   }
});

1
本来会选择这个作为采纳的答案,但Robin的回答更加深入。尽管如此,感谢您的回答 - 非常感谢 :-) - tjramage
@tjramage:选择他的答案作为正确答案是一个明智的决定。他的回答详细而精细,我也会点赞。 - Hovercraft Full Of Eels
+1 封装,例如使用 Action;同时考虑让控制器“监听”视图,如此处所建议:https://dev59.com/22LVa4cB1Zd3GeqP1PTg#10257197。 - trashgod
@tjramage 我在我的答案中添加了额外的编辑,试图解释我的观点并不一定是“正确”的,因为我会将控制器添加监听器的代码描述为良好、易于理解和可维护的代码。因此,这个答案也应该得到应有的 +1。 - Robin

5
注意:这个答案是我的个人观点。 我还需要找到关于编写Swing UI和将其与业务逻辑联系起来的“最佳/最推荐”方法的合适文献。 但在我找到它之前,我会运用常识和个人经验。
我认为两种方式都不是正确的实现方法。在MVC中,视图只是向用户提供视觉信息的部分。然而,当您按下按钮时,在ActionListener中触发的代码属于控制器,因为它很可能是业务逻辑(这是我根据您的代码片段做出的假设。如果您的按钮只执行UI操作,比如启用另一个组件,则应该完全包含在视图中的ActionListener)。
因此,我会在控制器中使这些方法公开,例如,如果您目前有一个ActionListener,类似于:
public void actionPerformed( ActionEvent e ){
  doSomeStuff( UI info, event info );
}

在这里,UI info是从视图中获取的信息,event info是从事件中获取的信息。我会在控制器上引入公共方法。

public void doSomeStuff( UI info, event info )

在视图中调用该方法有两个主要好处:
  1. 视图只依赖于控制器,而不是由控制器依赖于视图。如果控制器包含业务逻辑,那么很可能想要重用该代码以供更多的SwingUI使用。在此处不需要Swing类的依赖关系。例如单元测试。如果您的控制器不依赖于UI,则可以轻松测试应用程序的控制器-模型部分,并通过使用API来调用这些调用时遵循相同的代码路径。
  2. 您可以完全调整UI,并决定将按钮替换为另一个组件,而无需重写控制器和视图即可将其附加到 ActionListener上。

编辑

尽管我认为我帖子中的上述部分仍然有效,但我必须承认,在阅读trashgod答案评论中提到的维基百科MVC页面之后,上述内容仅是MVC模式的一种解释以及如何在Swing应用程序中实现它。

查看维基百科页面,它将MVC的三个组件定义为(摘要):

  • 模型:管理应用程序领域的行为和数据
  • 视图:将模型呈现为适合交互的形式,通常是用户界面元素
  • 控制器:接收用户输入并通过对模型对象进行调用来启动响应

我的解释/意见/首选方式是只有一个模型和一个控制器,如果需要多个视图(或例如在测试时零个视图)。控制器不会直接接收用户输入(这相当于让它在视图上注册侦听器),而是提供API钩子来将用户输入传递进来。这提供了一种“抽象控制器”,可以处理用户输入,但视图仍然需要将实际用户输入事件转换为控制器可理解的事件/信息。所以这意味着我可以很容易地创建MVC中的测试“视图”端,以及真正的“视图”端,而无需更改控制器或模型中的任何内容。

另一种解释是在控制器和视图之间有更紧密的耦合(它们确实彼此依赖)。这也意味着,如果您决定从Swing UI视图切换到命令行视图,则可能还需要更改控制器。这是更好/更糟的...我认为这是个人品味问题。

我原始答案中唯一不好的地方是将业务逻辑放在控制器部分,而维基百科页面明确指出它位于模型中。我的错误...


1
太棒了!可惜在我开始这个项目之前,在网上找不到类似的东西,否则我更愿意从头开始做。无论如何,这真的很有用 - 谢谢 :-) - tjramage

1

虽然我个人喜欢你的风格,但你会发现你朋友的风格(或类似的风格)更符合“标准Java”的规范。在典型的编程团队中,这将更加可接受。它还有可能的优点(和可能的陷阱),即在某些用例中,按钮可以被不同的按钮替换。


0
如果你的问题涉及到架构API(目前并不存在真正的API),那么我认为两种方式都不好,而且在你没有真正的经验和清晰的理解之前,不要使用任何模式,因为结果会很糟糕。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接