Skip to main content
added 83 characters in body
Source Link
gervais.b
  • 2.1k
  • 12
  • 13

The sequence diagrams were made with TextArt.io : https://textart.io/sequence

The sequence diagrams were made with TextArt.io : https://textart.io/sequence

Add the model, controller and final parts.
Source Link
gervais.b
  • 2.1k
  • 12
  • 13

A this time you should have a clean MVC. The advantages of this pattern is that you can easily test the model and the controller who are usually the most critical parts in an MVC. You can also extract your core business :(representation and execution of an equation) to reusable and testable classes and build your model (and sometimes controller) over them by applying the decorator and adapter patterns.

Please note that there are other popular pattern to build one application, Model View Presenter is one of them.

A this time you should have a clean MVC :

A this time you should have a clean MVC. The advantages of this pattern is that you can easily test the model and the controller who are usually the most critical parts in an MVC. You can also extract your core business (representation and execution of an equation) to reusable and testable classes and build your model (and sometimes controller) over them by applying the decorator and adapter patterns.

Please note that there are other popular pattern to build one application, Model View Presenter is one of them.

Add the model, controller and final parts.
Source Link
gervais.b
  • 2.1k
  • 12
  • 13

This will clean the "view" part. But, as already said, Swing is an MVC framework. So you are still missing the "m" and "c" parts...

Model

The model must be where your "state" is stored. Ideally this is the only class where you set and get the text. Let's create an Equation class for it and add all the required method on it (I amappend, clear, dropLast, ..)

So the Gui change this model when he receive an event from his ButtonsPanel. And you ends-up a line that is duplicate for each kind of event:

display.setText(equation.getText());

In Swing, the model is usually observable, so you can also add some listeners to react when he change. With this observable model your Gui will receive events form the buttons and update the model. When updated, the model will notify the text (via the event listener) and the text will change.

  +---------------+  +-----+     +-----------+    +-----------+   
  | ButtonsPanel  |  | Gui |     | Equation  |    | TextPanel |   
  +---------------+  +-----+     +-----------+    +-----------+   
          |             |              |                |         
          | onInput     |              |                |         
          |------------>|              |                |         
          |             |              |                |         
          |             | append       |                |         
          |             |------------->|                |         
          |             |              |                |         
          |             |              | onChange       |         
          |             |              |--------------->|         
          |             |              |                |         
          |             |              |                | setText 
          |             |              |                |-------- 
          |             |              |                |       | 
          |             |              |                |<------- 
          |             |              |                |     

You can notice that from one side, there is ane actor between the event and the model (the Gui between ButtonsPanel and Equation), while on the other side there is no actor between the model and one view (Equation to TextPanel). This incoherence lead us to a little architectural discussion.

It is up to you to decide who will listen and react to an event. But, in my case, I rely on the smart and dumb containers pattern. Where I have one smart component, the Gui and the two dumbs, ButtonsPanel and TextPanel. So that the Gui is a kind of mediator between the model and the views.

 +-----------+    +-----+       +---------------+ +-----------+
 | Equation  |    | Gui |       | ButtonsPanel  | | TextPanel |
 +-----------+    +-----+       +---------------+ +-----------+
       |             |                  |               |
       |             |         onInput  |               |
       |             |<-----------------|               |
       |             |                  |               |
       |      append |                  |               |
       |<------------|                  |               |
       |             |                  |               |
       | onChange    |                  |               |
       |------------>|                  |               |
       |             |                  |               |
       |             | setText          |               |
       |             |--------------------------------->|
       |             |                  |               |

In fact the Gui has too much responsibility, because it play both the role of a view (a smart container) and the role of a controller.

Controller

Let's segregate the roles of the Gui is role will be to deal with the presentation and redirect events to a controller. On the other side the controller will update the model according to the received actions.

In some systems the controller implements all the required listeners. This is sometime not the best one for code reuse because the controller depends on Swing. Also it require to expose the listeners of all of your components or create adapters between the listeners for the dumb components and those of the smart components. But it is useful to reduce the number of delegation or decoration methods. The biggest advantage is that it force you to create one model that represent the state of your application (or part of it) so that any change can be observed by the view.

Finally

A this time you should have a clean MVC :

 +-------+       +-------+              +---------------+ +-----------+ +-------------+
 | Model |       | View  |              | ButtonsPanel  | | TextPanel | | Controller  |
 +-------+       +-------+              +---------------+ +-----------+ +-------------+
     |               |                          |               |              |
     |               |         onButtonPressed  |               |              |
     |               |<-------------------------|               |              |
     |               |                          |               |              |
     |               | // call method for the pressed button    |              |
     |               |-------------------------------------------------------->|
     |               |                          |               |              |
     |               |                          |      // call mutation method |
     |<------------------------------------------------------------------------|
     |               |                          |               |              |
     | onChange      |                          |               |              |
     |-------------->|                          |               |              |
     |               |                          |               |              |
     |               | setText                  |               |              |
     |               |----------------------------------------->|              |
     |               |                          |               |              |
     
     

Model

class Model {

    private final EventListenerList listeners = new EventListenerList();
    private final StringBuilder content;

    public Model() {
        this.content = new StringBuilder();
    }

    public void setResult(String result) {
        fireOnResult(result);
    }

    public void append(String part) {
        content.append(part);
        fireOnChange();
    }

    public void clear() {
        content.delete(0, content.length());
        fireOnChange();
    }

    public void dropLast() {
        content.deleteCharAt(content.length());
        fireOnChange();
    }

    public String getText() {
        return content.toString();
    }

    public void addListener(ModelListener listener) {
        listeners.add(ModelListener.class, listener);
    }

    public void removeListener(ModelListener listener) {
        listeners.remove(ModelListener.class, listener);
    }

    private void fireOnChange() {
        ModelListener[] lstnrs = listeners.getListeners(ModelListener.class);
        for (int i=lstnrs.length-1; i > -1; i--) {
            lstnrs[i].onEquationChange(content.toString());
        }
    }

    private void fireOnResult(String result) {
        ModelListener[] lstnrs = listeners.getListeners(ModelListener.class);
        for (int i=lstnrs.length-1; i > -1; i--) {
            lstnrs[i].onResult(result);
        }
    }

}

View

class View extends JFrame {
    private final Controller controller;
    private final ButtonsPanel buttons;
    private final TextPanel display;

    public View(final Controller controller) {
        this.controller = controller;
        setSize(400, 400);
        setDefaultCloseOperation(EXIT_ON_CLOSE);

        add(display = new TextPanel(), BorderLayout.NORTH);
        add(buttons = new ButtonsPanel(), BorderLayout.SOUTH);

        buttons.addListener(new ButtonsListener());
        controller.addModelListener(new TextUpdater());
    }

    private final class TextUpdater implements ModelListener {
        private void updateText(String newText) {
            SwingUtilities.invokeLater(() -> display.setText(newText));
        }

        @Override
        public void onEquationChange(String equation) {
            updateText(equation);
        }

        @Override
        public void onResult(String result) {
            updateText(result);
        }
    }

    private final class ButtonsListener implements ButtonsPanel.ButtonsPanelListener {
        @Override
        public void onButtonPressed(String text) {
            new SwingWorker<Void, Void>(){
                @Override
                protected Void doInBackground() throws Exception {
                    switch (text) {
                        case ButtonsPanel.CLEAR:
                            controller.clear();
                            break;
                        case ButtonsPanel.ERASE:
                            controller.eraseOne();
                            break;
                        case ButtonsPanel.EQUAL:
                            controller.compute();
                            break;
                        default:
                            controller.onInput(text);
                    }
                    return null;
                }
            }.execute();
        }
    }

}

Controller

class Controller {

    private final Engine engine;
    private final Model model;

    public Controller(Engine engine) {
        this.engine = engine;
        this.model = new Model();
    }

    public void compute() throws Exception {
        String result = engine.compute(model.getText());
        model.setResult(result);
    }

    public void onInput(String text) {
        model.append(text);
    }


    public void clear() {
        model.clear();
    }


    public void eraseOne() {
        model.dropLast();
    }


    public void addModelListener(ModelListener listener) {
        model.addListener(listener);
    }

    public void removeModelListener(ModelListener listener) {
        model.removeListener(listener);
    }

}     

This will clean the "view" part. But, as already said, Swing is an MVC framework. So you are still missing the "m" and "c" parts... (I am on it)

This will clean the "view" part. But, as already said, Swing is an MVC framework. So you are still missing the "m" and "c" parts...

Model

The model must be where your "state" is stored. Ideally this is the only class where you set and get the text. Let's create an Equation class for it and add all the required method on it (append, clear, dropLast, ..)

So the Gui change this model when he receive an event from his ButtonsPanel. And you ends-up a line that is duplicate for each kind of event:

display.setText(equation.getText());

In Swing, the model is usually observable, so you can also add some listeners to react when he change. With this observable model your Gui will receive events form the buttons and update the model. When updated, the model will notify the text (via the event listener) and the text will change.

  +---------------+  +-----+     +-----------+    +-----------+   
  | ButtonsPanel  |  | Gui |     | Equation  |    | TextPanel |   
  +---------------+  +-----+     +-----------+    +-----------+   
          |             |              |                |         
          | onInput     |              |                |         
          |------------>|              |                |         
          |             |              |                |         
          |             | append       |                |         
          |             |------------->|                |         
          |             |              |                |         
          |             |              | onChange       |         
          |             |              |--------------->|         
          |             |              |                |         
          |             |              |                | setText 
          |             |              |                |-------- 
          |             |              |                |       | 
          |             |              |                |<------- 
          |             |              |                |     

You can notice that from one side, there is ane actor between the event and the model (the Gui between ButtonsPanel and Equation), while on the other side there is no actor between the model and one view (Equation to TextPanel). This incoherence lead us to a little architectural discussion.

It is up to you to decide who will listen and react to an event. But, in my case, I rely on the smart and dumb containers pattern. Where I have one smart component, the Gui and the two dumbs, ButtonsPanel and TextPanel. So that the Gui is a kind of mediator between the model and the views.

 +-----------+    +-----+       +---------------+ +-----------+
 | Equation  |    | Gui |       | ButtonsPanel  | | TextPanel |
 +-----------+    +-----+       +---------------+ +-----------+
       |             |                  |               |
       |             |         onInput  |               |
       |             |<-----------------|               |
       |             |                  |               |
       |      append |                  |               |
       |<------------|                  |               |
       |             |                  |               |
       | onChange    |                  |               |
       |------------>|                  |               |
       |             |                  |               |
       |             | setText          |               |
       |             |--------------------------------->|
       |             |                  |               |

In fact the Gui has too much responsibility, because it play both the role of a view (a smart container) and the role of a controller.

Controller

Let's segregate the roles of the Gui is role will be to deal with the presentation and redirect events to a controller. On the other side the controller will update the model according to the received actions.

In some systems the controller implements all the required listeners. This is sometime not the best one for code reuse because the controller depends on Swing. Also it require to expose the listeners of all of your components or create adapters between the listeners for the dumb components and those of the smart components. But it is useful to reduce the number of delegation or decoration methods. The biggest advantage is that it force you to create one model that represent the state of your application (or part of it) so that any change can be observed by the view.

Finally

A this time you should have a clean MVC :

 +-------+       +-------+              +---------------+ +-----------+ +-------------+
 | Model |       | View  |              | ButtonsPanel  | | TextPanel | | Controller  |
 +-------+       +-------+              +---------------+ +-----------+ +-------------+
     |               |                          |               |              |
     |               |         onButtonPressed  |               |              |
     |               |<-------------------------|               |              |
     |               |                          |               |              |
     |               | // call method for the pressed button    |              |
     |               |-------------------------------------------------------->|
     |               |                          |               |              |
     |               |                          |      // call mutation method |
     |<------------------------------------------------------------------------|
     |               |                          |               |              |
     | onChange      |                          |               |              |
     |-------------->|                          |               |              |
     |               |                          |               |              |
     |               | setText                  |               |              |
     |               |----------------------------------------->|              |
     |               |                          |               |              |
     
     

Model

class Model {

    private final EventListenerList listeners = new EventListenerList();
    private final StringBuilder content;

    public Model() {
        this.content = new StringBuilder();
    }

    public void setResult(String result) {
        fireOnResult(result);
    }

    public void append(String part) {
        content.append(part);
        fireOnChange();
    }

    public void clear() {
        content.delete(0, content.length());
        fireOnChange();
    }

    public void dropLast() {
        content.deleteCharAt(content.length());
        fireOnChange();
    }

    public String getText() {
        return content.toString();
    }

    public void addListener(ModelListener listener) {
        listeners.add(ModelListener.class, listener);
    }

    public void removeListener(ModelListener listener) {
        listeners.remove(ModelListener.class, listener);
    }

    private void fireOnChange() {
        ModelListener[] lstnrs = listeners.getListeners(ModelListener.class);
        for (int i=lstnrs.length-1; i > -1; i--) {
            lstnrs[i].onEquationChange(content.toString());
        }
    }

    private void fireOnResult(String result) {
        ModelListener[] lstnrs = listeners.getListeners(ModelListener.class);
        for (int i=lstnrs.length-1; i > -1; i--) {
            lstnrs[i].onResult(result);
        }
    }

}

View

class View extends JFrame {
    private final Controller controller;
    private final ButtonsPanel buttons;
    private final TextPanel display;

    public View(final Controller controller) {
        this.controller = controller;
        setSize(400, 400);
        setDefaultCloseOperation(EXIT_ON_CLOSE);

        add(display = new TextPanel(), BorderLayout.NORTH);
        add(buttons = new ButtonsPanel(), BorderLayout.SOUTH);

        buttons.addListener(new ButtonsListener());
        controller.addModelListener(new TextUpdater());
    }

    private final class TextUpdater implements ModelListener {
        private void updateText(String newText) {
            SwingUtilities.invokeLater(() -> display.setText(newText));
        }

        @Override
        public void onEquationChange(String equation) {
            updateText(equation);
        }

        @Override
        public void onResult(String result) {
            updateText(result);
        }
    }

    private final class ButtonsListener implements ButtonsPanel.ButtonsPanelListener {
        @Override
        public void onButtonPressed(String text) {
            new SwingWorker<Void, Void>(){
                @Override
                protected Void doInBackground() throws Exception {
                    switch (text) {
                        case ButtonsPanel.CLEAR:
                            controller.clear();
                            break;
                        case ButtonsPanel.ERASE:
                            controller.eraseOne();
                            break;
                        case ButtonsPanel.EQUAL:
                            controller.compute();
                            break;
                        default:
                            controller.onInput(text);
                    }
                    return null;
                }
            }.execute();
        }
    }

}

Controller

class Controller {

    private final Engine engine;
    private final Model model;

    public Controller(Engine engine) {
        this.engine = engine;
        this.model = new Model();
    }

    public void compute() throws Exception {
        String result = engine.compute(model.getText());
        model.setResult(result);
    }

    public void onInput(String text) {
        model.append(text);
    }


    public void clear() {
        model.clear();
    }


    public void eraseOne() {
        model.dropLast();
    }


    public void addModelListener(ModelListener listener) {
        model.addListener(listener);
    }

    public void removeModelListener(ModelListener listener) {
        model.removeListener(listener);
    }

}     

added 10 characters in body
Source Link
gervais.b
  • 2.1k
  • 12
  • 13
Loading
Source Link
gervais.b
  • 2.1k
  • 12
  • 13
Loading