Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to separate Swing GUI from Business Logic when Spring etc. is not used

please be advised, this is a long post. Sorry for that but I want to make my point clear:

I was wondering how to separate Swing GUI from Presentation and Business Logic for quite a long time. At work I had to implement a 3 MD Excel Export for some data with a small Swing Dialog to configure the export. We do not use a framework like Spring for this so I had to implement it myself.

I wanted to completely separate GUI from Business Logic, which are in precise following tasks:

  • Tell BL to start its job from GUI
  • Report Progress from BL to GUI
  • Report Logging from BL to GUI
  • Delegate BL Result to GUI

of course the GUI shouldnt have notice of the BL implementation and vice versa. I created several interfaces for all those tasks above, e. g. a ProgressListener, LogMessageListener, JobDoneListener, etc., to be fired by the Business Logic. For instance, if the Business Logic wants to tell about logging, it calls

fireLogListeners("Job has been started");

classes that implement the public interface LogListener + are attached to the BL, now will be notified about the "Job has been started" log message. All these listeners are at this time implemented by the GUI itself, which in general looks like this:

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.
}

The "GUI and BL creating class" simply attaches the GUI (as all these listeners' interface) to the BL, which looks something like this:

exportJob.addProgressListener(uiDialog);
exportJob.addLogListener(uiDialog);
exportJob.addJobFinishedListener(uiDialog);
exportJob.start();

I am now quite unsure about that because it looks weird because of all those newly created listener Interfaces. What do you think about? How do you separate your Swing GUI components from BL?

Edit: For better demonstrating purpose I created a Demo workspace in eclipse file-upload.net/download-9065013/exampleWorkspace.zip.html I pasted it to pastebin also, but better import those classes in eclipse, pretty a lot of code http://pastebin.com/LR51UmMp

like image 275
Stefano L Avatar asked Jun 15 '14 10:06

Stefano L


2 Answers

A few things.

I would no have the uiDialog code in the ExportFunction class. The whole perform method should just be code in the main class. The ExportFunctions responsibility is to 'export' not to 'show 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() is not needed)

You seem to be overengineering a fair bit. I don't know why you would expect to have many threads to be running at the same time. When you press the button, you would only expect one thread to run right? Then there would be no need to have an array of actionPerformedListener.

instead of this :

button.addActionListener(new ActionListener() {

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

});

why not just :

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

button.addActionListener(new ActionListener() {

    @Override
    public void actionPerformed(ActionEvent e) {
        exportJob.start();
    }
});

That way you can get rid of ExportFunction which doesn't really serve any purpose.

You seem to have a lot of arrays of listeners. Unless you really really need them I wouldn't bother with them and keep it as simple as possible.

Instead of :

Thread.sleep(1000);
fireLogListener("Excel Sheet 2 created");
Thread.sleep(1000);

Just have :

Thread.sleep(1000);
log("Excelt Sheet 1 created");
Thread.sleep(1000);

where log is :

private void log(final String message) {
    ((DefaultListModel<String>) list.getModel()).addElement(message);
}

This way you are keeping it simpler and cleaner.

GUI should not know about BL, but BL somehow has to tell the GUI what to do. You can abstract ad infinitum with lots of interfaces, but in 99.99% of applications this is not necessary, especially yours which seems fairly simple.

So while the code you have written is pretty good, i would try and simplify and reduce the interfaces. It doesn't warrant that much engineering.

like image 100
Oliver Watkins Avatar answered Oct 18 '22 22:10

Oliver Watkins


Basically, your architecture seems ok to me. I suppose you wonder if it is because of the numerous listeners you set up.

A solution for this might be either:

a) to have a generic Event class, with subclasses for specific events. You could use a visitor to implement the actual listeners.

b) to use an Event Bus (see guava, for instance). With an event bus architecture, your model will publish events to the event bus, and your UI objects will listen for events from the event bus, and filter them.

Some systems can even use annotations for declaring listener methods.

like image 2
khaemuaset Avatar answered Oct 18 '22 21:10

khaemuaset