Java 当不使用Spring等时,如何将Swing GUI与业务逻辑分离

Java 当不使用Spring等时,如何将Swing GUI与业务逻辑分离,java,swing,oop,decoupling,cohesion,Java,Swing,Oop,Decoupling,Cohesion,请注意,这是一个很长的帖子。很抱歉,但我想说清楚: 很长一段时间以来,我一直在想如何将SwingGUI与表示和业务逻辑分开。 在工作中,我必须使用一个小的Swing对话框来配置一些数据的3MDExcel导出。 我们没有使用像Spring这样的框架,所以我必须自己实现它 我想将GUI与业务逻辑完全分离,业务逻辑具体包括以下任务: 告诉BL从GUI开始它的工作 从BL向GUI报告进度 报告从BL到GUI的日志记录 将BL结果委托给GUI 当然,GUI不应该注意BL的实现,反之亦然。 我为上面所有

请注意,这是一个很长的帖子。很抱歉,但我想说清楚:

很长一段时间以来,我一直在想如何将SwingGUI与表示和业务逻辑分开。 在工作中,我必须使用一个小的Swing对话框来配置一些数据的3MDExcel导出。 我们没有使用像Spring这样的框架,所以我必须自己实现它

我想将GUI与业务逻辑完全分离,业务逻辑具体包括以下任务:

  • 告诉BL从GUI开始它的工作
  • 从BL向GUI报告进度
  • 报告从BL到GUI的日志记录
  • 将BL结果委托给GUI
当然,GUI不应该注意BL的实现,反之亦然。 我为上面所有这些任务创建了几个接口,例如。G
ProgressListener
LogMessageListener
JobDoneListener
, 等,由业务逻辑触发。例如,如果业务逻辑想要说明日志记录,它会调用

fireLogListeners("Job has been started");
实现公共接口LogListener+的类附加到BL,现在将收到关于“作业已启动”日志消息的通知。 此时,所有这些侦听器都由GUI本身实现,通常如下所示:

public class ExportDialog extends JDialog implements ProgressListener, LogListener, JobFinishedListener,  ErrorListener {

    @Override
    public void jobFinished(Object result){
        // Create Save File dialog and save exported Data to file.
    }

    @Override
    public void reportProgress(int steps){
        progressBar.setValue(progressBar.getValue()+steps);
    }

    @Override
    public void errorOccured(Exception ex, String additionalMessage){
        ExceptionDialog dialog = new ExceptionDialog(additionalMessage, ex);
        dialog.open();
    }

    // etc.
}
exportJob.addProgressListener(uiDialog);
exportJob.addLogListener(uiDialog);
exportJob.addJobFinishedListener(uiDialog);
exportJob.start();
“GUI和BL创建类”只是将GUI(作为所有这些侦听器的接口)附加到BL,如下所示:

public class ExportDialog extends JDialog implements ProgressListener, LogListener, JobFinishedListener,  ErrorListener {

    @Override
    public void jobFinished(Object result){
        // Create Save File dialog and save exported Data to file.
    }

    @Override
    public void reportProgress(int steps){
        progressBar.setValue(progressBar.getValue()+steps);
    }

    @Override
    public void errorOccured(Exception ex, String additionalMessage){
        ExceptionDialog dialog = new ExceptionDialog(additionalMessage, ex);
        dialog.open();
    }

    // etc.
}
exportJob.addProgressListener(uiDialog);
exportJob.addLogListener(uiDialog);
exportJob.addJobFinishedListener(uiDialog);
exportJob.start();
我现在很不确定,因为它看起来很奇怪,因为所有这些新创建的侦听器接口。 你觉得怎么样? 如何将Swing GUI组件与BL分离

编辑: 为了更好地演示,我在eclipse文件-upload.net/download-9065013/exampleWorkspace.zip.html中创建了一个演示工作区 我也将它粘贴到了pastebin中,但是最好在eclipse中导入这些类,相当多的代码

还有一些东西

我不会在ExportFunction类中使用uiDialog代码。整个perform方法应该只是主类中的代码。ExportFunctions的职责是“导出”而不是“显示gui”

public static void main(String[] args) {
    ExportFunction exporter = new ExportFunction();

    final ExportUIDialog uiDialog = new ExportUIDialog();
    uiDialog.addActionPerformedListener(exporter);

    uiDialog.pack();
    uiDialog.setVisible(true);
}
(不需要Swing.invokeLater())

你似乎有点过度设计了。我不知道为什么您希望同时运行多个线程。当您按下按钮时,您只希望一个线程正常运行?这样就不需要有actionPerformedListener数组

与此相反:

button.addActionListener(new ActionListener() {

    @Override
    public void actionPerformed(ActionEvent arg0) {
        if (startConditionsFulfilled()) {
            fireActionListener(ActionPerformedListener.STARTJOB);
        }
    }

});
为什么不只是:

final ExportJob exportJob = new ExportJob();
exportJob.addJobFinishedListener(this);
exportJob.addLogListener(this);

button.addActionListener(new ActionListener() {

    @Override
    public void actionPerformed(ActionEvent e) {
        exportJob.start();
    }
});
这样,您就可以摆脱ExportFunction,它实际上不起任何作用

您似乎有很多侦听器数组。除非你真的需要它们,否则我不会为它们费心,尽量简单

而不是:

Thread.sleep(1000);
fireLogListener("Excel Sheet 2 created");
Thread.sleep(1000);
只要有:

Thread.sleep(1000);
log("Excelt Sheet 1 created");
Thread.sleep(1000);
其中日志为:

private void log(final String message) {
    ((DefaultListModel<String>) list.getModel()).addElement(message);
}
私有无效日志(最终字符串消息){
((DefaultListModel)list.getModel()).addElement(消息);
}
这样,您可以使它更简单、更干净

GUI不应该知道BL,但BL必须以某种方式告诉GUI该做什么。你可以用很多接口无限抽象,但在99.99%的应用程序中,这是不必要的,尤其是你的应用程序,它看起来相当简单


因此,虽然您编写的代码非常好,但我会尝试简化和减少接口。它不保证有那么多的工程设计。

基本上,我觉得你的架构还不错。我想你可能想知道这是否是因为你建立了众多的听众

解决方案可能是:

a) 有一个通用事件类,带有特定事件的子类。 您可以使用访问者来实现实际的侦听器

b) 使用事件总线(例如,参见guava)。 使用事件总线体系结构,您的模型将向事件总线发布事件, 您的UI对象将侦听事件总线中的事件,并对其进行过滤


有些系统甚至可以使用注释来声明侦听器方法。

我认为您的方向是合理的。您通过观察者模式提供事件通知的想法是合理的,我也不反对以这种方式分离侦听器,因为它为您提供了在不重叠责任的情况下更改通知对象的灵活性。对接口进行编码也是一种很好的方法。关于你能做的唯一的事情是一个工厂模式,通过某种动态加载过程来加载BL逻辑,但除此之外,我认为你正在朝着正确的方向前进。我会看看这个模式。这是分层GUI应用程序的最常见方法。您的方法已经完成了一半,但我认为创建附加层将增加更多的灵活性。i、 你需要从演示者中分割视图。我喜欢你的设计!你的出口工作是很好的解除供应,你没有摇摆组件在那里。谢谢你的意见!您的设计很好,但是您必须小心线程问题:业务逻辑不应该在Swing事件调度线程中运行。但是Swing组件应该始终从事件分派线程使用。因此,您的侦听器应该使用SwingUtilities.invokeLater()在接收到来自业务逻辑的事件时更新UI。您好,现在我认为应用程序耦合良好。我无法替换业务逻辑实现“ExportJob”,即。G使用子类,因为它是硬编码的导出作业。这就是程序员所说的“FactoryPattern”的意思,即在更改业务逻辑实现时更加灵活。此外,如果我正确理解ExportJob类中直接使用log()方法,则可以将BL直接绑定到UIDialog类。如果让控制器处理事件侦听,则可以减少ExportJob上的绑定。这样,您将只有一个从控制器到UI的引用。我想我要说的是,除非你想开发一个框架(比如EclipseOSGi),否则