Java 3个Swing应用程序设计:哪一个是最好的?

Java 3个Swing应用程序设计:哪一个是最好的?,java,model-view-controller,swing,architecture,Java,Model View Controller,Swing,Architecture,我是桌面应用程序开发的新手,今年夏天我有一个相当大的项目要交付。问题是,代码必须非常清晰,所以当我更新它时,我不会遇到(很多)麻烦 因此,我想要一个良好的“关注点分离”。对我来说,最困难的部分是视图-控制器分离 现在,我已经阅读了很多教程、讨论等,我用3种不同的方式设计了一个迷你应用程序。该应用程序很简单:点击一个按钮,将标签转换为“Hello world” 你觉得这三种设计怎么样 是否有更好的设计来满足我的期望 设计1 View1.java: public View1() { init

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

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

现在,我已经阅读了很多教程、讨论等,我用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");
            }
}

这些设计我都不喜欢。您正在将控制器与视图紧密耦合。假设您希望在将来更改控制器实现,因此必须进入所有类并更改类。相反,你应该让它注射。有很多lib可以通过诸如或之类的注释来为您实现这一点,但我不打算详细介绍这些。这是一个更好的设计

public class View{
private Controller controller;
   public View(Controller controller) {
       this.controller = controller;
   }
}
这是一个更简洁的设计,因为视图不必知道控制器的实现是什么。您可以稍后创建一个子类并传递它

现在,通过上面的设计,我认为您可以看到不应该将视图传递给控制器。这又是一个不好的耦合。相反,您可以传递一个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);
}
如果你仔细看,我建议你移除任何类型的耦合。视图不应要求控制器,它应在上给出。控制器不应该知道视图,而应该执行回调。这一点很重要,因为若您决定不使用Swing,那个么您的控制器中就不会有所有这些对Swing包的依赖


希望这一切都有帮助

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

设计问题1:视图方面太复杂。额外的方法使它看起来像里面有一个控制器。简单的更改将变得难以实现

设计3的问题:这会给控制器带来太多压力。控制器不应该知道正在发生什么Swing事件。在这种设计中,如果希望基于JList而不是JButton执行操作,则必须更改视图和控制器,这是错误的

关于代码的其他注释:

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

决定哪种模式最好在很大程度上取决于您要解决的问题,因此,你必须考虑是否在它上面加上另一层间接值是值得的。 由于您是UI编程新手,我建议您首先整合系统的一部分,然后根据您从中学到的知识,确定您的体系结构。设计良好的体系结构使测试和重用组件变得容易。和是两种著名的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);
    }
}
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);
    }
}
MVVP

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

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

型号

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();
            }
        });
    }
}
这就是应用程序的外观:

我认为没有唯一正确的答案,这取决于您希望如何连接现有组件

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


另请参见此和相关内容。哇,这是很酷的东西。如果我做对了,你将一个类传递给控制器,这个类在其他一些进程之后被控制器调用。但是如果我的回调类变大了怎么办?您建议在view.java中还是在另一个类中编写它?如果回调方法变得更复杂,那么我认为正确的修改位置仍然在视图中。这里的想法是视图应该负责自己的回调,而不是另一个实体。我不建议在其他类中编写它,因为您必须将所有swing组件传递给其他类。这能完成很多吗?尝试将计算数据的类与更新UI的类解耦。当用户界面发生变化时,这将是未来的一大胜利。@user777466已经有一段时间了。你有机会看看这是否对你有用吗?我真的很喜欢你提出的MVP设计,我很清楚。不过我还是要检查一下Swing体系结构概述的链接,因为我感觉可能有一些隐藏的机制可以节省我一些时间。我个人在工作时在Swing遗留代码库上使用MVP。与MVVM相比,你必须创建更多的类,测试起来有点困难,但你得到了很多可重用性。答案不错,但不幸的是链接已经失效。我一直在项目的模型方面工作。但我仍然对全球设计感到好奇。我的视图中会有很多数据,可能会被用户更改。最好的选择是在视图和模型之间进行一些直接链接:当文本字段发生更改时,模型对象