3 Swing应用程序设计:哪个最好?

时间:2011-08-05 17:08:41

标签: java model-view-controller swing architecture

我是桌面应用程序开发的新手,今年夏天我有一个非常大的项目。问题是代码必须非常清晰,所以当我更新代码时,我不会遇到麻烦。

因此,我想要一个良好的“关注点分离”。对我来说最困难的部分是视图 - 控制器分离。

现在,我已经阅读了很多教程,讨论等。我已经以3种不同的方式设计了一个迷你应用程序。该应用程序很简单:单击一个将标签转换为“Hello world”的按钮。

您如何看待这三种设计?

有没有更好的设计来满足我的期望?

设计1

View1.java:

public View1() {
    initComponents();
    this.controller = new Controller1(this);
}

private Controller1 controller;

public void updateLabel(String message){
    this.jLabel1.setText(message);
}

private void jButton1ActionPerformed(java.awt.event.ActionEvent evt) {
    this.controller.doSomething();
}

private void initComponents() {
...
jButton1.addActionListener(new java.awt.event.ActionListener() {
        public void actionPerformed(java.awt.event.ActionEvent evt) {
            jButton1ActionPerformed(evt);
        }
    });
...}

Controller1.java:

public class Controller1 {
    public Controller1(View1 v){
        this.view = v;
    }

    public void doSomething(){
        this.view.updateLabel("Hello world");
    }

    private View1 view;
}

设计2

View2.java:

public View2() {
        initComponents();
        this.controller = new Controller2(this);

        jButton1.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                controller.doSomething();
            }
        });
    }
    public void updateLabel(String message){
        this.jLabel1.setText(message);
    }
    private Controller2 controller;
  ...

}

Controller2.java:

public class Controller2 {

        public Controller2(View2 v){
            this.view = v;
        }

        public void doSomething(){
            this.view.updateLabel("Hello world");
        }

        private View2 view;
}

设计3

View3.java:

public View3() {
        initComponents();
        this.controller = new Controller3(this);
        this.jButton1.addActionListener(this.controller.listener);
    }
    private Controller3 controller;
    public void updateLabel(String message){
        this.jLabel1.setText(message);
    }
...}

Controller3.java:

public class Controller3 {

    public Controller3(View3 v){
        this.view = v;
        this.listener = new MyListener(v);
    }

    private View3 view;
    public MyListener listener;
}

MyListener.java:

public class MyListener implements ActionListener{
    private View3 view;

    public MyListener(View3 v){
        this.view = v;
    }

    public void actionPerformed(java.awt.event.ActionEvent evt) {
                this.view.updateLabel("Hello world");
            }
}

4 个答案:

答案 0 :(得分:10)

我不喜欢这些设计。您将控制器与视图紧密耦合。假设您希望将来更改控制器实现,因此您必须进入所有类并更改类。相反,你应该注入它。有许多lib可以通过GuiceSpring等注释为您完成此操作,但我不会参与其中。这是一个更好的设计。

public class View{
private Controller controller;
   public View(Controller controller) {
       this.controller = controller;
   }
}

这是一个更清洁的设计,因为视图不必知道控制器的实现是什么。您可以稍后创建一个子类并将其传递给它。

所以现在通过上面的设计,我认为你可以看到你不应该将View传递给控制器​​。这又是耦合,这是不好的。相反,你可以传递一个onCallback类,它将在完成后执行。这是代码来代替它

jButton1.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent evt) {
            controller.doSomething(new Runnable(){
                    public void run(){
                        updateLabel("Hello world");
                    }               
           });
       }
});

然后在你的控制器中

public void doSomething(Runnable callback){
   // do work
   SwingUtilties.invokeLater(callback);
}

如果你看起来我建议删除任何类型的耦合。该视图不应该要求控制器,应该给予它。 Controller不应该知道应该只执行回调的视图。这很重要,因为如果您决定不使用Swing,那么您的控制器中的Swing包就不会具有所有这些依赖关系。

希望这一切都有帮助!

答案 1 :(得分:6)

确定哪种模式最佳取决于您正在解决的问题。 Swing is already an MVC framework,所以你必须考虑在其上添加另一层间接是值得的。

由于您不熟悉UI编程,我建议您首先将walking skeleton系统放在一起,然后根据您从中学到的内容,决定您的架构。精心设计的架构使测试和重用组件变得容易。 MVPMVVM是两种众所周知的UI设计模式。

对于你的玩具问题,你可以像我一样实现MVP或MVVM。请记住,您通常也会在每个接口之间使用接口,如果可以改变,将在模型上有观察者。

<强> MVP

public class Model {
    public String getWhatIWantToSay() {
        return "Hello World";
    }
}

public class Presenter implements ActionListener {
    private final View view;
    private final Model model;
    public Presenter(Model model, View view) {
        this.model = model;
        this.view = view;
        view.addButtonListener(this);
    }
    public void actionPerformed(ActionEvent e) {
        view.setText(model.getWhatIWantToSay());
    }
}

public class View {
    private JButton button = new JButton();
    private JLabel label = new JLabel();
    public void addButtonListener(ActionListener listener) {
        button.addActionListener(listener);
    }
    public void setText(String text) {
        label.setText(text);
    }
}

<强> MVVP

public class ModelView extends Observable {
    private final Model model;
    private String text = "";

    public ModelView(Model model) {
        this.model = model;
    }

    public void buttonClicked() {
        text = model.getWhatIWantToSay();
        notifyObservers();
    }
}

public class View implements Observer {
    private JButton button = new JButton();
    private JLabel label = new JLabel();
    private final ModelView modelView;

    public View(final ModelView modelView) {
        this.modelView = modelView;
        modelView.addObserver(this);
        button.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                modelView.buttonClicked();
            }
        });
    }

    public void update(Observable o, Object arg) {
        label.setText(modelView.text);
    }
}

答案 2 :(得分:3)

我认为设计2是满足您标准的最佳选择。

设计1的问题:在视图方面太复杂了。额外的方法使它看起来几乎就像它里面有一个控制器。实施简单的改变将变得复杂。

设计3的问题:这会对控制器造成太大影响。控制器不应该知道Swing事件发生了什么。在该设计中,如果您希望基于JList而不是JButton执行操作,则必须更改视图和控制器,这是不好的。

关于您的代码的其他评论:

  • 使用import语句,因此您不必在代码中包含类的包,如:java.awt.event.ActionListener()
  • 如果没有必要,可以在几个地方使用this.,这只会增加噪音。
  • 正如Amir指出的那样,您的视图和控制器之间的耦合非常紧密,这是不必要的。

答案 3 :(得分:0)

另一种设计方法可能是这样的:

<强>模型

package biz.tugay.toypro.model;

public interface LabelService {
    String getDateInRandomLocale();
}

package biz.tugay.toypro.model;

import java.text.DateFormat;
import java.util.Calendar;
import java.util.Locale;
import java.util.concurrent.ThreadLocalRandom;

public class LabelServiceImpl implements LabelService {

    private final Locale availableLocalesJava[];

    public LabelServiceImpl() {
        this.availableLocalesJava = DateFormat.getAvailableLocales();
    }

    @Override
    public String getDateInRandomLocale() {
        final int randomIndex = ThreadLocalRandom.current().nextInt(0, availableLocalesJava.length);
        final Locale locale = availableLocalesJava[randomIndex];
        final Calendar calendar = Calendar.getInstance();
        final DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.LONG, locale);
        return dateFormat.format(calendar.getTime());
    }
}

查看

package biz.tugay.toypro.view;

import biz.tugay.toypro.model.LabelService;

import javax.swing.*;

public class DateInRandomLocaleLabel extends JLabel {

    private final LabelService labelService;

    public DateInRandomLocaleLabel(final LabelService labelService) {
        this.labelService = labelService;
    }

    public void showDateInRandomLocale() {
        final String dateInRandomLocale = labelService.getDateInRandomLocale();
        setText(dateInRandomLocale);
    }
}

package biz.tugay.toypro.view;

import javax.swing.*;

public class RandomizeDateButton extends JButton {

    public RandomizeDateButton() {
        super("Hit Me!");
    }
}

package biz.tugay.toypro.view;

import javax.swing.*;
import java.awt.*;

public class DateInRandomLocalePanel extends JPanel {

    public DateInRandomLocalePanel(final JLabel dateInRandomLocaleLabel, final JButton randomizeDateButton) {
        final GridLayout gridLayout = new GridLayout(1, 2);
        setLayout(gridLayout);

        add(dateInRandomLocaleLabel);
        add(randomizeDateButton);
    }
}

package biz.tugay.toypro.view;

import javax.swing.*;

public class MainFrame extends JFrame {

    public void init() {
        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        setSize(400, 50);
        setVisible(true);
    }
}

<强>控制器

package biz.tugay.toypro.controller;

import biz.tugay.toypro.view.DateInRandomLocaleLabel;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

public class RandomizeDateButtonActionListener implements ActionListener {

    private DateInRandomLocaleLabel dateInRandomLocaleLabel;

    @Override
    public void actionPerformed(final ActionEvent e) {
        dateInRandomLocaleLabel.showDateInRandomLocale();
    }

    public void setDateInRandomLocaleLabel(final DateInRandomLocaleLabel dateInRandomLocaleLabel) {
        this.dateInRandomLocaleLabel = dateInRandomLocaleLabel;
    }
}

最后我如何启动应用程序:

package biz.tugay.toypro;

import biz.tugay.toypro.controller.RandomizeDateButtonActionListener;
import biz.tugay.toypro.model.LabelService;
import biz.tugay.toypro.model.LabelServiceImpl;
import biz.tugay.toypro.view.DateInRandomLocaleLabel;
import biz.tugay.toypro.view.DateInRandomLocalePanel;
import biz.tugay.toypro.view.MainFrame;
import biz.tugay.toypro.view.RandomizeDateButton;

import javax.swing.*;

public class App {

    public static void main(String[] args) {
        final LabelService labelService = new LabelServiceImpl();

        // View
        final DateInRandomLocaleLabel dateInRandomLocaleLabel = new DateInRandomLocaleLabel(labelService);
        final RandomizeDateButton randomizeDateButton = new RandomizeDateButton();

        final DateInRandomLocalePanel dateInRandomLocalePanel = new DateInRandomLocalePanel(dateInRandomLocaleLabel, randomizeDateButton);
        final MainFrame mainFrame = new MainFrame();
        mainFrame.getContentPane().add(dateInRandomLocalePanel);

        // Controller
        final RandomizeDateButtonActionListener randomizeDateButtonActionListener = new RandomizeDateButtonActionListener();

        // Bind Controller to the View..
        randomizeDateButton.addActionListener(randomizeDateButtonActionListener);

        // Bind View to the Controller..
        randomizeDateButtonActionListener.setDateInRandomLocaleLabel(dateInRandomLocaleLabel);

        // Show the main frame..
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                mainFrame.init();
            }
        });
    }
}

这就是应用程序的样子: enter image description here

enter image description here

enter image description here

我认为没有一个正确的答案,这取决于你想如何绑定你拥有的组件..

您可能会发现以下内容也很有用: