Student information demo in Swing using MVC

Posted on

Problem

I’ve followed several guides on how to implement MVC in Java, and so far I’ve come up with the classes shown below. From my understanding, MVC follows these guidelines.

  • The Model should be independent and unaware of the View and Controller.
  • The View should wait for interaction from the user and inform the Controller.
  • The Controller gets and sets information in the Model and updates the View if necessary.
  • It’s possible for the View to reference the Model. (This is what I don’t understand).

Model:

public class Student_Model {

    private String fname, lname;

    public String getFirstName(){
        return this.fname;
    }
    public String getLastName(){
        return this.lname;
    }
    public void setFirstName(String newFname){
        this.fname = newFname;
    }
    public void setLastName(String newLname){
         this.lname = newLname;
    }

}

View

public class Student_View {

    public JPanel panel;
    public JTextField student_fname, student_lname;
    public JLabel stu_fname_label, stu_lname_label;
    public JButton changeName;

    public Student_View(){

        panel = new JPanel();
        panel.setPreferredSize(new Dimension(200,200));
        panel.setBackground(Color.red);

        student_fname = new JTextField(20);
        student_lname = new JTextField(20);

        stu_fname_label = new JLabel("First Name: ");
        stu_lname_label = new JLabel("Last Name: ");

        changeName = new JButton("Change student data");

        panel.add(stu_fname_label);
        panel.add(student_fname);
        panel.add(stu_lname_label);
        panel.add(student_lname);
        panel.add(changeName);

        panel.setVisible(true);

    }
}

Controller

public class Student_Controller implements ActionListener {

    Student_Model student_model;
    Student_View student_view;

    public Student_Controller(Student_Model sm, Student_View sv){
        this.student_model = sm;
        this.student_view = sv;

        initActionListeners(this);
    }

    public void actionPerformed(ActionEvent e) {
        student_model.setFirstName("Bill");
        System.out.println(student_model.getFirstName());

    }
    public void initActionListeners(ActionListener al){
        student_view.changeName.addActionListener(al);

    }

}

And finally it’s put together in the as such:

public class MVC_Design_Demo extends JFrame {

    public MVC_Design_Demo(){
        setPreferredSize(new Dimension(500,500));
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setResizable(false);
        setLocationRelativeTo(null);
        getContentPane().setBackground(Color.black);

        init();

    }
    public void init(){

        Student_Model newStudent = retrieveStudentInfo();
        Student_View student_view = new Student_View();
        add(student_view.panel);
        Student_Controller student_control = new Student_Controller(newStudent, student_view);

    }

    public static void main(String[] args){
        MVC_Design_Demo mvc = new MVC_Design_Demo();
        mvc.setVisible(true);
        mvc.pack();

    }
    public static Student_Model retrieveStudentInfo(){
        Student_Model sm = new Student_Model();
        sm.setFirstName("John");
        sm.setLastName("Smith");
        return sm;

    }
}

Does this follow the MVC design pattern and are my above assumptions about the guidelines MVC should follow correct?

Solution

First of all MVC is not a “real” design pattern as it lacks atomicity. It’s something that adresses some points of the SOLID-principles, especially the single responsibility principle. Furthermore you may use design patterns to realize MVC. If you follow the SOLID-principles carefully you will certainly come up with this 3-class struture in your usecase anyway.

Secondly you apply the MVC correctly. It’s good to have a starting point that is more concrete to most of the developers as the SOLID-principles. These developers will produce better code if they use this “best practise” as I would call it. This “best practise” provides a good balance between productivity and code quality even though there are many more improvements possible.

One example:
Currently two classes are technology dependent: Student_View and Student_Controller. Both know Swing as the UI technology. It’s better to have less classes depending on technology. A naive way is to melt controller and ui to have only one class. But that violates the single responsibility principle (S in SOLID) as the class wil get MORE responsibilities as it has before. We would take a step backwards if we do so. So the task should be to preserve responsibilities AND decrease technology dependencies.

How to do that:

Use the listener pattern! Let the UI implement the Listener-Interface and register it in the controller so the controller does only know “Listeners”. The principle applied here is “dependency inversion” (D in SOLID).

What I want to say is: Yes, you did apply MVC correctly. But is this the core? No. Is it the end? No. It’s something you will come up with as a intermediate result if you use the real design patterns mentioned in this book (Design Patterns: Elements of reusable Object-Oriented software) AND apply the SOLID principles and the law of demeter correctly.

Some points to your code in detail:

  1. You have public/package scope variables that should be private scope
  2. You tear apart the Button construction as it should contain the ActionListener declaration as well
  3. Use lazy getter instantiation for your UI elements and use these getter only

So you have some issues in encapsulation, locality and dependency.

I provide you an advanced version that meets these requirements.

public class MVC_Design_Demo extends JFrame {

    public MVC_Design_Demo() {

        add(new Student_View());

        setPreferredSize(new Dimension(500, 500));
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        setResizable(false);
        setLocationRelativeTo(null);
        getContentPane().setBackground(Color.black);

    }

    public static void main(String[] args) {
        MVC_Design_Demo mvc = new MVC_Design_Demo();
        mvc.setVisible(true);
        mvc.pack();
    }

}


public class Student_Controller {


    private Student_Model newStudent;


    private Set<StudentListener> listeners;

    public Student_Controller() {
        this.newStudent = retrieveStudentInfo();
        this.listeners = new HashSet<>();
    }


    public static Student_Model retrieveStudentInfo() {

        Student_Model sm = new Student_Model();

        sm.setFirstName("John");
        sm.setLastName("Smith");

        return sm;
    }


    public void addListener(StudentListener listener) {
        this.listeners.add(listener);
        listener.registeredAsListener(this.newStudent);
    }


    public void setFirstName(String fName) {
        this.newStudent.setFirstName(fName);
        notifyOnFirstNameChanged();
    }


    private void notifyOnFirstNameChanged() {
        for (StudentListener listener : listeners) {
            listener.onFirstNameChanged(this.newStudent.getFirstName());
        }
    }


}


public class Student_Model {

    private String fname, lname;

    public String getFirstName() {
        return this.fname;
    }

    public String getLastName() {
        return this.lname;
    }

    public void setFirstName(String newFname) {
        // Some business logic
        if (newFname.equals("Bill")) {
            this.fname = newFname + "y";
        } else {
            this.fname = newFname;
        }
    }

    public void setLastName(String newLname) {
        this.lname = newLname;
    }
}


public class Student_View extends Panel implements StudentListener {


    private JTextField student_fname, student_lname;
    private JLabel stu_fname_label, stu_lname_label;
    private JButton changeName;


    private Student_Controller controller;


    public Student_View(){

        setPreferredSize(new Dimension(200,200));
        setBackground(Color.red);
        add(getLabelStuFName());
        add(getTextFieldStudentFName());
        add(getLabelLName());
        add(getTextFieldStudentLName());
        add(getButtonChangeName());

        setVisible(true);

        this.controller = new Student_Controller();
        this.controller.addListener(this);

    }


    private JButton getButtonChangeName() {
        if (changeName == null) {
            changeName = new JButton("Change student data");
            changeName.addActionListener(new ActionListener() {

                @Override
                public void actionPerformed(ActionEvent e) {
                    controller.setFirstName("Bill");
                }

            });
        }
        return changeName;
    }


    private JLabel getLabelLName() {
        if (stu_lname_label == null) {
            stu_lname_label = new JLabel("Last Name: ");
        }
        return stu_lname_label;
    }


    private JLabel getLabelStuFName() {
        if (stu_fname_label == null) {
            stu_fname_label = new JLabel("First Name: ");
        }
        return stu_fname_label;
    }


    private JTextField getTextFieldStudentLName() {
        if (student_lname == null) {
            student_lname = new JTextField(20);
        }
        return student_lname;
    }


    private JTextField getTextFieldStudentFName() {
        if (student_fname == null) {
            student_fname = new JTextField(20);
        }
        return student_fname;
    }


    @Override
    public void registeredAsListener(Student_Model newStudent) {
        this.getTextFieldStudentFName().setText(newStudent.getFirstName());
        this.getTextFieldStudentLName().setText(newStudent.getLastName());
    }


    @Override
    public void onFirstNameChanged(String firstName) {
        this.getTextFieldStudentFName().setText(firstName);
    }


}


public interface StudentListener {

    void registeredAsListener(Student_Model newStudent);

    void onFirstNameChanged(String firstName);

}

Some annotations:

In the model there is now a little business logic to illustrate that you may get an adapted result to present to the ui. (“Bill” will be converted to “Billy”)

All former public or package scope variables are private now so you can ensure they will not be manipulated accidentially.

Lazy initialization is introduced for all ui components. The single responsibility principle was applied here. Furthermore, lazy initialization makes the code more flexible as you remove the neccessity to care about the instantiation order/timepoint.

The creation of the ActionListener is now part of the button creation. It was removed from the controller. Previously it was neccessary to have the Button public to access the “addActionListener”-method. The Button is private now. Furthermore you established a “long” dependency to the method “addActionListener”. The “law of demeter” was applied, I shortend the dependency. This was the first step to make the controller independent from Swing.

The introduction of the listener pattern is the second step to make the controller independent from Swing. (dependency inversion)

The model informs the listener once when the listener is registered. So the listener can initialize itself.

The model notifies all listeners if the first name changes.

The model is created within the UI class. Now you have some less classes knowing the model. (less dependencies)

Leave a Reply

Your email address will not be published. Required fields are marked *