MVC模式和摆动

2022-08-31 11:50:15

在“真正的摇摆生活”中,我发现最难真正掌握的设计模式之一是MVC模式。我已经在这个网站上浏览了不少讨论该模式的帖子,但我仍然不觉得我对如何在我的Java Swing应用程序中利用该模式有清晰的理解。

假设我有一个JFrame,其中包含一个表,几个文本字段和几个按钮。我可能会使用TableModel将JTable与底层数据模型“连接起来”。但是,负责清除字段、验证字段、锁定字段以及按钮操作的所有函数通常都会直接进入 JFrame。但是,这不是混合了模式的控制器和视图吗?

据我所知,在查看JTable(和模型)时,我设法“正确”地实现了MVC模式,但是当我将整个JFrame视为一个整体时,事情变得混乱。

我真的很想听听其他人对此的看法。当您需要使用 MVC 模式向用户显示表格、几个字段和一些按钮时,您该怎么做?


答案 1

我强烈推荐给你的一本MVC in swing的书是Freeman和Freeman的“Head First Design Patterns”。他们对MVC有非常全面的解释。

小结

  1. 您是用户 - 您与视图交互。视图是模型的窗口。当您对视图执行某些操作(如单击“播放”按钮)时,视图会告诉控制器您执行了什么操作。控制器的工作是处理这个问题。

  2. 控制器要求模型更改其状态。控制器接受您的操作并对其进行解释。如果单击某个按钮,控制器的工作是弄清楚这意味着什么,以及如何根据该操作操作操作模型。

  3. 控制器还可以要求更改视图。当控制器从视图中接收到操作时,它可能需要告诉视图进行更改。例如,控制器可以启用或禁用界面中的某些按钮或菜单项。

  4. 模型在视图的状态发生更改时通知视图。当模型中的某些内容根据您采取的某些操作(如单击按钮)或其他一些内部更改(如播放列表中的下一首歌曲已启动)而发生更改时,模型会通知视图其状态已更改。

  5. 视图向模型请求状态。视图直接从模型中获取它显示的状态。例如,当模型通知视图新歌曲已开始播放时,视图会从模型中请求歌曲名称并显示它。视图还可能要求模型提供状态,作为控制器请求在视图中进行某些更改的结果。

enter image description here来源(如果您想知道什么是“奶油控制器”,请考虑奥利奥饼干,控制器是奶油中心,视图是顶部饼干,模型是底部饼干。

嗯,如果你有兴趣,你可以从这里下载一首关于MVC模式的相当有趣的歌曲!

Swing 编程可能面临的一个问题涉及将 SwingWorker 和 EventDispatch 线程与 MVC 模式合并。根据您的程序,您的视图或控制器可能必须扩展 SwingWorker 并重写放置资源密集型逻辑的方法。这可以很容易地与典型的MVC模式融合,并且是典型的Swing应用。doInBackground()

编辑#1

此外,将MVC视为各种模式的一种组合也很重要。例如,可以使用观察者模式(要求将视图注册为模型的观察者)实现模型,而控制器可能使用 Strategy 模式。

编辑#2

我还想具体回答你的问题。您应该在视图中显示表格按钮等,这显然会实现一个操作Listener。在方法中,检测事件并将其发送到控制器中的相关方法(请记住,视图包含对控制器的引用)。所以当一个按钮被点击时,事件被视图检测到,发送到控制器的方法,控制器可能会直接要求视图禁用按钮什么的。接下来,控制器将与模型交互并修改模型(该模型将主要具有 getter 和 setter 方法,以及其他一些用于注册和通知观察者等的方法)。一旦模型被修改,它将调用已注册观察者的更新(这将是您情况下的视图)。因此,视图现在将自行更新。actionPerformed()


答案 2

不喜欢视图应该是模型在其数据更改时通知的视图。我会将该功能委托给控制器。在这种情况下,如果更改应用程序逻辑,则无需干扰视图的代码。视图的任务仅用于应用程序组件+布局仅此而已。摆动布局已经是一项繁琐的任务,为什么要让它干扰应用程序逻辑呢?

我对MVC的想法(我目前正在使用,到目前为止一切都很好)是:

  1. 景色是三者中最愚蠢的。它对控制器和模型一无所知。它关注的只是摆动部件的工艺和布局。
  2. 模型也很愚蠢,但不像视图那么愚蠢。它执行以下功能。
  • a. 当控制器调用其一个 setter 时,它将向其侦听器/观察者发出通知(就像我说的,我会将此角色降级为控制器)。我更喜欢SwingPropertyChangeSupport来实现这一点,因为它已经为此目的进行了优化。
  • b. 数据库交互功能。
  1. 一个非常智能的控制器。非常了解视图和模型。控制器有两个功能:
  • 一个。它定义视图在用户与其交互时将执行的操作。
  • b.它侦听模型。就像我说的,当调用模型的设置者时,模型将向控制器发出通知。控制器的工作是解释此通知。它可能需要反映对视图的更改。

代码示例

景观 :

就像我说的,创建视图已经很详细了,所以只需创建自己的实现:)

interface View{
    JTextField getTxtFirstName();
    JTextField getTxtLastName();
    JTextField getTxtAddress();
}

出于可测试性目的,将这三者连接起来是理想的。我只提供了模型和控制器的实现。

型号 :

public class MyImplementationOfModel implements Model{
    ...
    private SwingPropertyChangeSupport propChangeFirer;
    private String address;
    private String firstName;
    private String lastName;

    public MyImplementationOfModel() {
        propChangeFirer = new SwingPropertyChangeSupport(this);
    }
    public void addListener(PropertyChangeListener prop) {
        propChangeFirer.addPropertyChangeListener(prop);
    }
    public void setAddress(String address){
        String oldVal = this.address;
        this.address = address;
        
        //after executing this, the controller will be notified that the new address has been set. Its then the controller's
        //task to decide what to do when the address in the model has changed. Ideally, the controller will update the view about this
        propChangeFirer.firePropertyChange("address", oldVal, address);
    }
    ...
    //some other setters for other properties & code for database interaction
    ...
}

控制器 :

public class MyImplementationOfController implements PropertyChangeListener, Controller{

    private View view;
    private Model model;

    public MyImplementationOfController(View view, Model model){
        this.view = view;
        this.model = model;
        
        //register the controller as the listener of the model
        this.model.addListener(this);
        
        setUpViewEvents();
    }

    //code for setting the actions to be performed when the user interacts to the view.
    private void setUpViewEvents(){
        view.getBtnClear().setAction(new AbstractAction("Clear") { 
            @Override
            public void actionPerformed(ActionEvent arg0) {
                model.setFirstName("");
                model.setLastName("");
                model.setAddress("");
            }
        });
        
        view.getBtnSave().setAction(new AbstractAction("Save") { 
            @Override
            public void actionPerformed(ActionEvent arg0) {
                ...
                //validate etc.
                ...
                model.setFirstName(view.getTxtFName().getText());
                model.setLastName(view.getTxtLName().getText());
                model.setAddress(view.getTxtAddress().getText());
                model.save();
            }
        });
    }
    
    public void propertyChange(PropertyChangeEvent evt){
        String propName = evt.getPropertyName();
        Object newVal = evt.getNewValue();
        
        if("address".equalsIgnoreCase(propName)){
            view.getTxtAddress().setText((String)newVal);
        }
        //else  if property (name) that fired the change event is first name property
        //else  if property (name) that fired the change event is last name property
    }
}

设置 MVC 的主 :

public class Main{
    public static void main(String[] args){
        View view = new YourImplementationOfView();
        Model model = new MyImplementationOfModel();
        
        ...
        //create jframe
        //frame.add(view.getUI());
        ...
        
        //make sure the view and model is fully initialized before letting the controller control them.
        Controller controller = new MyImplementationOfController(view, model);
        
        ...
        //frame.setVisible(true);
        ...
    }
}

推荐