Page 1 of 1

Separating the GUI and Logic with a StateManager Rate Topic: -----

#1 macosxnerd101  Icon User is offline

  • Self-Trained Economist
  • member icon




Reputation: 10567
  • View blog
  • Posts: 39,121
  • Joined: 27-December 08

Posted 26 December 2011 - 08:00 PM

*
POPULAR

One of the biggest mistakes new programmers make, especially when beginning to work with GUIs, is to mix the business logic with the UI code. This is poor practice for a number of reasons. First, it makes it difficult to isolate the data to ensure it's integrity. There is a saying in this industry- if a piece of data is in two places at once, it is wrong at both places. Redundancy in this fashion makes it difficult to ensure data integrity. For example, if one class modifies a count variable, and a corresponding variable is contained in another class, that second variable may not be updated in the second location. By separating the business logic from the UI, this problem can easily be avoided.

A second reason not separating the business logic from the UI is that it makes it hard to move to another UI platform. For example, by separating the business logic, it is easily develop to use a Swing GUI, a console application, or an Android application without rewriting more portions of the framework than necessary.

For this tutorial, the case study will be a simple Student enrollment program. I have included the data classes to be used in the business logic, including the Course, Student, Teacher, and School classes. Since this is a tutorial about using a StateManager, I am simply going to attach these classes for reference. They are very simple and straight-forward.


The Student class models a simple Student, with a name, id, and a list of Courses.
Spoiler


The Course class includes the name, id, and department for the given Course. It also contains the roster of Students enrolled in this Course.
Spoiler


The Teacher class is pretty straight-forward, simply encapsulating a name and list of Courses.
Spoiler


And finally, the School class encapsulates the Students, Teachers, and Courses.
Spoiler


Now that the basic framework has been designed, let's work on defining the requirements of the application to help when designing the StateManager. This application will allow the user to:
-Add or Remove Teachers, Students, and Courses
-Update Students
-Enroll Students in Courses
-Assign Courses to Teachers

The next step is to design the StateManager. This class serves as the intermediary between the data and business logic, and the user interface, providing one clear point of access to the state and data. The StateManager will be a singleton class, making it a global for the application.
package statemanager.school;

import java.util.*;

/**
 * @author Michael Levet
 * @date 12/23/2011
 * 
 * This class facilitates the interactions 
 * between the user interface and the application's
 * logic and state.
 ***/
public class StateManager {

    private static final StateManager manager = new StateManager();
    private School school;

    private StateManager() {
        school = new School();
    }

    public static StateManager getInstance() {
        return manager;
    }

    public void addTeacher(String name) {
        school.addTeacher(new Teacher(name));
    }

    public Teacher getTeacher(String name) {
        return school.getTeacher(name);
    }

    public Teacher getTeacher(int index) {
        return school.getTeacher(index);
    }

    public void removeTeacher(String name) {
        school.removeTeacher(school.getTeacher(name));
    }

    public List<Teacher> getAllTeachers() {
        return new ArrayList<Teacher>(school.getAllTeachers());
    }

    public void addStudent(String name, String id) {
        school.addStudent(new Student(name, id));
    }

    public Student getStudent(String name) {
        return school.getStudent(name);
    }

    public Student getStudent(int index) {
        return school.getStudent(index);
    }

    public void removeStudent(String name) {
        school.removeStudent(school.getStudent(name));
    }

    public List<Student> getAllStudents() {
        return new ArrayList<Student>(school.getAllStudents());
    }

    public void addCourse(String name, String id, String department) {
        school.addCourse(new Course(name, id, department));
    }

    public Course getCourse(int index) {
        return school.getCourse(index);
    }

    public Course getCourse(String name) {
        return school.getCourse(name);
    }

    public void removeCourse(String name) {
        school.removeCourse(school.getCourse(name));
    }

    public List<Course> getAllCourses() {
        return new ArrayList<Course>(school.getAllCourses());
    }

    public void enrollStudent(String courseName, String studentName) {
        Course c = school.getCourse(courseName);
        Student s = school.getStudent(studentName);
        c.addStudent(s);
    }

    public void dropStudentFromCourse(String courseName, String studentName) {
        Course c = school.getCourse(courseName);
        Student s = school.getStudent(studentName);
        c.removeStudent(s);
    }

    public void addCourseForTeacher(String courseName, String teacherName) {
        Course c = school.getCourse(courseName);
        Teacher t = school.getTeacher(teacherName);
        t.addCourse(c);
    }
}



Now that the framework and StateManager have been created, let's take a look at how the StateManager should be used by a GUI component in the application. The basic idea is that the GUI components will use the appropriate getter and setter methods from the StateManager to populate and update the data for the application.

Let's start with the ManageStudentsFrame class. For the purpose of this tutorial, it is simplified to allow basic adding, editing, and deleting of Students. The Student names are listed by JRadioButtons, which are created by accessing the StateManager. Notice how the JRadioButtons have no actual ties to the Student objects- there is no Object association beyond setting the text of the JRadioButtons to the Student names.
package statemanager.gui;

import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import statemanager.school.*;

/**
 * @author Michael Levet
 * @date 12/25/2011
 ***/
public class ManageStudentsFrame extends JFrame implements ActionListener {

    private JPanel mainPanel, buttonPane, optionsPane;
    private JRadioButton[] buttons;
    private JLabel title;
    private StateManager manager;
    private ButtonGroup group;
    private JButton add, edit, delete;

    public ManageStudentsFrame() {
        super("Manage Students");
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        manager = StateManager.getInstance();

        mainPanel = new JPanel(new BorderLayout());

        title = new JLabel("Select a Student to Edit or Delete It.");
        title.setHorizontalAlignment(JLabel.CENTER);
        mainPanel.add(title, BorderLayout.NORTH);

        buttonPane = new JPanel(new GridLayout(0, 1));

        mainPanel.add(buttonPane, BorderLayout.CENTER);

        optionsPane = new JPanel();
        add = new JButton("Add New Student");
        edit = new JButton("Edit Student");
        delete = new JButton("Delete");

        add.addActionListener(this);
        edit.addActionListener(this);
        delete.addActionListener(this);

        optionsPane.add(add);
        optionsPane.add(edit);
        optionsPane.add(delete);

        mainPanel.add(optionsPane, BorderLayout.SOUTH);

        this.add(mainPanel);
        refreshStudents();
        pack();
        setLocationRelativeTo(null);
        setVisible(true);
    }

    /**
     * This method is used to repopulate the Students list
     * as a group of JRadioButtons. It obtains the Students 
     * list from the StateManager.
     ***/
    protected void refreshStudents() {
        java.util.List<Student> students = manager.getAllStudents();
        buttons = new JRadioButton[students.size()];
        group = new ButtonGroup();
        buttonPane.removeAll();

        for (int i = 0; i < students.size(); i++) {
            buttons[i] = new JRadioButton(students.get(i).getName());
            group.add(buttons[i]);
            buttonPane.add(buttons[i]);
        }
        pack();
    }

    public void actionPerformed(ActionEvent e) {

        if (e.getSource() == edit) {
            Student s = manager.getStudent(getSelected().getText());
            new EditStudentsDialog(s, this);
            return;
        } else if (e.getSource() == delete) {
            Student s = manager.getStudent(getSelected().getText());
            int option = JOptionPane.showConfirmDialog(this, "Delete Student",
                    "Are you sure you want to delete the Student "
                    + s.getName(), JOptionPane.YES_NO_CANCEL_OPTION);

            if (option == JOptionPane.YES_OPTION) {
                manager.removeStudent(s.getName());
                JOptionPane.showMessageDialog(null, "Student deleted");
            }

            this.refreshStudents();
            return;
        } else if (e.getSource() == add) {
            new EditStudentsDialog(null, this);
        }
    }

    private JRadioButton getSelected() {
        for (JRadioButton b : this.buttons) {
            if (b.isSelected()) {
                return b;
            }
        }
        return null;
    }

    public static void main(String[] args) {
        new ManageStudentsFrame();
    }
}



When the add and edit JButtons are pressed, a custom JDialog is displayed allowing the user to edit the name and text of the Student. While generally the GUI is not supposed to contain data elements, this JDialog does contain a Student as an instance field. This Student object is obtained from the StateManager in the ManageStudentsFrame actionPerformed() method. This is not a design concern, as the JDialog only updates what was obtained from the StateManager rather than continuing to move data across the GUI components.
package statemanager.gui;

import javax.swing.*;
import java.awt.event.*;
import statemanager.school.*;

/**
 * @author Michael Levet
 * @date 12/26/2011
 ***/
public class EditStudentsDialog extends JDialog implements ActionListener {

    private Student student;
    private JTextField name, id;
    private JLabel nameLabel, idLabel;
    private JButton submit;
    private StateManager manager;
    private ManageStudentsFrame parent;

    public EditStudentsDialog(ManageStudentsFrame parent) {
        this(null, parent);
    }

    public EditStudentsDialog(Student s, ManageStudentsFrame parent) {
        this.student = s;
        this.parent = parent;

        this.setLocationRelativeTo(null);
        this.setModalityType(ModalityType.APPLICATION_MODAL);

        manager = StateManager.getInstance();

        Box main = Box.createVerticalBox();

        Box first = Box.createHorizontalBox();
        nameLabel = new JLabel("Name:");
        name = new JTextField(15);

        first.add(nameLabel);
        first.add(name);


        Box second = Box.createHorizontalBox();
        idLabel = new JLabel("ID:");
        id = new JTextField(15);

        second.add(idLabel);
        second.add(id);

        submit = new JButton("Submit");
        submit.addActionListener(this);

        main.add(first);
        main.add(second);
        main.add(submit);

        this.add(main);

        if (s != null) {
            name.setText(s.getName());
            id.setText(s.getId());
        }

        this.pack();
        this.setVisible(true);
    }

    public void actionPerformed(ActionEvent e) {
        String newName = name.getText().trim();
        String newId = id.getText().trim();

        if (newName.length() == 0 || newId.length() == 0) {
            JOptionPane.showMessageDialog(this, "You must enter both a name and id for the Student");
            return;
        }

        if (student == null) {
            manager.addStudent(newName, newId);
        } //end if
        else {
            student.setName(newName);
            student.setId(newId);
        }

        parent.refreshStudents();
        this.setVisible(false);
        this.dispose();
    } //end actionPerformed()
}



Conclusion- While this tutorial did not cover designing the entire application, notice the important part- how the GUI used the StateManager to access the necessary data to populate its components, and to update the data as well.

Is This A Good Question/Topic? 15
  • +

Replies To: Separating the GUI and Logic with a StateManager

#2 blackcompe  Icon User is offline

  • D.I.C Lover
  • member icon

Reputation: 1155
  • View blog
  • Posts: 2,533
  • Joined: 05-May 05

Posted 27 December 2011 - 11:39 AM

Nice tutorial Mac. Good architecture is such a valuable skill. Good job.
Was This Post Helpful? 0
  • +
  • -

#3 skorned  Icon User is offline

  • New D.I.C Head

Reputation: 13
  • View blog
  • Posts: 41
  • Joined: 30-August 08

Posted 27 December 2011 - 12:39 PM

Thanks for the tutorial mac. When I was beginning Java development, after having done some work with MVC frameworks for the web, I found it quite frustrating that I was actually encouraged by all the beginner guides and even my teacher to mix the view and the logic. I tried to abstract out things a bit, but was not able to come up with a satisfactory solution without data binding.

However, I don't quite like the implementation of your approach, it needs a lot of repetition, and DRY is just as important as separation of view and logic. I would never be able to get myself to write that bunch of code that just repackages school functions as Statemanager functions, such as

public Teacher getTeacher(int index) {
        return school.getTeacher(index);
    }



And the solution is also not ideal because it seems the GUI still has to be aware of the functions in the logic, and access stuff such as StateManager.getTeacher(3). In the perfect world, the gui should be just a template with holes that the logic fills in, and when events occur, it should notify the logic which can then handle it. There are quite a few MVC frameworks for Java out there that provide tools to make this happen. I know you haven't quite said anywhere that you're trying to follow the MVC paradigm, but since we're on the topic of separating GUI and logic, I thought Java beginners should be aware of the options.

Cheers,
Neil
Was This Post Helpful? 1
  • +
  • -

#4 blackcompe  Icon User is offline

  • D.I.C Lover
  • member icon

Reputation: 1155
  • View blog
  • Posts: 2,533
  • Joined: 05-May 05

Posted 27 December 2011 - 01:31 PM

Quote

There are quite a few MVC frameworks for Java out there that provide tools to make this happen. I know you haven't quite said anywhere that you're trying to follow the MVC paradigm, but since we're on the topic of separating GUI and logic, I thought Java beginners should be aware of the options.


Exactly why I'm moving to Spring.
Was This Post Helpful? 0
  • +
  • -

#5 macosxnerd101  Icon User is offline

  • Self-Trained Economist
  • member icon




Reputation: 10567
  • View blog
  • Posts: 39,121
  • Joined: 27-December 08

Posted 27 December 2011 - 02:32 PM

@skorned: Those are some valid points. Personally, I don't see the MVC pattern and a StateManager as having to be mutually exclusive. My preference in design would be to provide the models what they need, and have them be responsible for executing necessary updates not possible with the current data through the StateManager. For this tutorial, my focus wasn't MVC, though my GUI could have been cleaned up to better adhere to the MVC and Observer patterns. I'll have to look into these MVC frameworks though. This sounds like a good follow-up tutorial. :)
Was This Post Helpful? 0
  • +
  • -

#6 pbl  Icon User is offline

  • There is nothing you can't do with a JTable
  • member icon

Reputation: 8332
  • View blog
  • Posts: 31,857
  • Joined: 06-March 08

Posted 28 December 2011 - 10:17 PM

Not against your StateManager but do not see any reason for it to be a SingleTon
Was This Post Helpful? 0
  • +
  • -

#7 macosxnerd101  Icon User is offline

  • Self-Trained Economist
  • member icon




Reputation: 10567
  • View blog
  • Posts: 39,121
  • Joined: 27-December 08

Posted 28 December 2011 - 10:47 PM

The reason the StateManager is a Singleton is because there only needs to be one instance of it in the program. There is no need for multiple StateManagers in the application. And also, not making the StateManager a singleton raises the issue of where to instantiate it in the application. How does the GUI access the instance of the StateManager? What class do we store the instance in? There are lots of application architecture issues that arise by not designing the StateManager as a singleton. It is cleaner to StateManager.getInstance() than to try and force a new StateManager() as an instance field elsewhere. :)
Was This Post Helpful? 2
  • +
  • -

Page 1 of 1