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

那么,鉴于上述两种情况,哪种方法更好呢?

谢谢。

4

4 回答 4

5

我当前的设计将视图中需要侦听器的所有组件声明为 public final。然后它将视图传递给控制器​​。从那里,控制器可以完全控制访问组件并添加动作侦听器。代码示例:

不,不要不必要地公开字段——你不应该为了 MVC 而牺牲封装。出于上述原因,您不应该公开组件。而是提供控件可以调用的视图公共方法,或者通过模型的侦听器让视图更改状态。将控件传递给视图,将视图传递给控件,​​所有交互都应该通过公共方法,而不是通过暴露组件或字段。

我已经解决了这个问题的一种方法,我并不是说这是最好的或规范的方法是将 Control 的实例传递给 View 并为我的侦听器使用匿名内部类,并让这些类调用控制方法。例如,

// inside of view
exitButton.addActionListener(new ActionListener() {
   public void actionPerformed(ActionEvent evt) {
      if (control != null) {
         control.exitAction();
      }
   }
});
于 2012-04-23T18:16:02.180 回答
5

注意:这个答案是我个人的看法。我仍然需要找到关于编写 Swing UI 并将其与业务逻辑联系起来的“最佳/最推荐”方式的体面文献。但在我发现这一点之前,我会使用常识和个人经验。

我想说这两者都不是实现这一点的正确方法。在 MVC 中,视图只是您向用户提供视觉信息的部分。但是,ActionListener当您按下按钮时触发的代码是属于控制器的代码,因为它很可能是业务逻辑(这是我根据您的代码片段做出的假设。如果您的按钮只是执行 UI 操作,例如启用另一个组件,动作监听器应该完全包含在视图中)。

所以我会在控制器中公开这些方法,例如,如果你目前有一个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 遵循与通过 UI 调用这些调用时相同的代码路径。
  2. 您可以完全调整您的 UI,并决定将按钮替换为另一个您无法附加的组件,ActionListener而无需重写您的控制器和视图。

编辑

虽然我认为我帖子的上述部分仍然有效,但我必须承认,在阅读了垃圾神回答中提到的维基百科 MVC 页面(他在其中一个评论中提到)之后,以上只是对 MVC 模式的一种解释和如何在 Swing 应用程序中实现这一点。

查看那个 Wiki 页面,它将 MVC 中的三个组件定义为(总结):

  • 模型:管理应用程序域的行为和数据
  • View:将模型渲染成适合交互的形式,通常是用户界面元素
  • 控制器:接收用户输入并通过调用模型对象来发起响应

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

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

在我的原始答案中唯一不太好的是我将业务逻辑定位在控制器部分,而 Wiki 页面明确指出它位于模型中。我可能对一些简单的 Ruby on Rails 实验感到困惑,其中模型只不过是一个数据访问层。我的错 ...

于 2012-04-23T18:20:59.303 回答
1

虽然我个人喜欢你的风格,但你会发现你朋友的风格(或类似的)更“标准的 Java-like”。因此,在一个典型的编程团队中,它会更容易接受。它还具有(可能的)优势(和可能的陷阱),即在某些用例中可以用不同的按钮替换按钮。

于 2012-04-23T18:20:32.437 回答
0

如果您的问题与架构 API(目前不是真正的 API)有关,那么我认为这两种方式都不好,而且在您没有真正的经验和清楚理解它之前不要使用任何模式,因为结果会不好。

于 2012-04-23T18:13:05.383 回答