Шаблон наблюдателя в MVC для определенных полей - PullRequest
1 голос
/ 20 апреля 2019

В шаблоне MVC, который является лучшим вариантом для Модели, чтобы уведомить Представление (если это правильный подход в первую очередь), где из всех полей данных, которые хранит Модель, только пара из нихобновляются.В частности, когда мы хотим обновить только определенные поля представления.

В настоящее время я использую шаблон MVC с Observer / Subscriber (JAVA Swing), как описано здесь: https://stackoverflow.com/a/6963529, но когда модель обновляется,он изменяет все в представлении при вызове функции update(), невозможно определить, какое поле из модели изменилось, чтобы обновить только обязательное поле в представлении.

Я читал эту тему: https://softwareengineering.stackexchange.com/a/359008 и это также: https://stackoverflow.com/a/9815189, что, на мой взгляд, полезно, но в дальнейшем я не очень хорошо понимаю, как я могу установить propertyChangeListener для переменной (int, float и т. Д.).С этим также связано: https://stackoverflow.com/a/9815189

Основной класс, в котором запускается программное обеспечение:

public class Main {
    public static void main(String[] args) {
        Model m = new Model();
        View v = new View(m);
        Controller c = new Controller(m, v);
        c.initController();
    }
}

Итак, код, который у меня есть в Model, такой:

public class Model extends Observable {
   //...
   private float speed;
   private int batteryPercentage;

   public float getSpeed() {
       return speed;
   }
   public void setSpeed(float speed) {
       this.speed = speed;
       setChanged();
       notifyObservers();
   }

    public int getBatteryPercentage() {
        return batteryPercentage;
    }
    public void setBatteryPercentage(int batteryPercentage) {
        this.batteryPercentage = batteryPercentage;
        setChanged();
        notifyObservers();
    }
}

Представление знает модель:

public class View implements Observer {
    private Model model;
    private JTextField txtFldSpeed;
    private JTextField txtFldBattery;
    private JFrame mainWindow;

    public View(Model m) {
        this.model = m;
        initialize();
    }
    private void initialize() {
        mainWindow = new JFrame();
        mainWindow.setTitle("New Window");
        mainWindow.setMinimumSize(new Dimension(1280, 720));
        mainWindow.setBounds(100, 100, 1280, 720);
        mainWindow.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

        JPanel tPanel1 = new JPanel();
        tPanel1.setBorder(new LineBorder(new Color(0, 0, 0)));
        tPanel1.setLayout(null);
        mainWindow.getContentPane().add(tPanel1);

        mainWindow.getContentPane().add(tPanel1);
        txtFldSpeed = new JTextField();
        txtFldSpeed.setEditable(false);
        txtFldSpeed.setBounds(182, 11, 116, 22);
        tPanel1.add(txtFldSpeed);

        txtFldBattery = new JTextField();
        txtFldBattery.setEditable(false);
        txtFldBattery.setBounds(182, 43, 116, 22);
        tPanel1.add(txtFldBattery);

        mainWindow.setVisible(true);
    }
    @Override
    public void update(Observable o, Object arg) {
        txtFldSpeed.setText(Float.toString(model.getSpeed()) + " kn");
        txtFldBattery.setText(Integer.toString(model.getBatteryPercentage()) + " %");
    }
}

Контроллер добавляет представление в качестве наблюдателя модели:

public class Controller {
    private Model model;
    private View view;

    public Controller(Model m, View v) {
        this.model = m;
        this.view = v;
    }

    public void initController() {    
        model.addObserver(view);
        model.setSpeed(10);
    }
}

То, что я ожидаю, это то, что, когда модельобновляется, скажем, вызывается функция setSpeed(), View сообщается, что ей нужно обновить себя в этом конкретном поле, а не в каждом «изменяемом» поле (например, txtFldBattery.

. Я хочусделать это потому, что в представлении есть поля, которые обновляются пару раз в секунду, и потому что мне нужно обновить все в представлении, JComboBox, который не нужно обновлять так часто, продолжает закрываться при попытке выборавариант.

Ответы [ 2 ]

2 голосов
/ 20 апреля 2019

Я бы использовал SwingPropertyChangeSupport, чтобы каждое из полей состояния модели было «связанным свойством», чтобы каждое поле состояния можно было прослушивать отдельно.

Например, скажем, у вас есть модель, которая выглядит следующим образом:

public class MvcModel {
    public static final String SPEED = "speed";
    public static final String BATTERY = "battery";
    public static final int MAX_SPEED = 40;
    private float speed;
    private int batteryPercentage;
    private SwingPropertyChangeSupport pcSupport = new SwingPropertyChangeSupport(this);

    public float getSpeed() {
        return speed;
    }

    public void setSpeed(float speed) {
        float oldValue = this.speed;
        float newValue = speed;
        this.speed = speed;
        pcSupport.firePropertyChange(SPEED, oldValue, newValue);
    }

    public int getBatteryPercentage() {
        return batteryPercentage;
    }

    public void setBatteryPercentage(int batteryPercentage) {
        int oldValue = this.batteryPercentage;
        int newValue = batteryPercentage;
        this.batteryPercentage = batteryPercentage;
        pcSupport.firePropertyChange(BATTERY, oldValue, newValue);
    }

    public void addPropertyChangeListener(PropertyChangeListener listener) {
        pcSupport.addPropertyChangeListener(listener);
    }

    public void removePropertyChangeListener(PropertyChangeListener listener) {
        pcSupport.removePropertyChangeListener(listener);
    }

    public void addPropertyChangeListener(String name, PropertyChangeListener listener) {
        pcSupport.addPropertyChangeListener(name, listener);
    }

    public void removePropertyChangeListener(String name, PropertyChangeListener listener) {
        pcSupport.removePropertyChangeListener(name, listener);
    }

}

И поля speed, и batteryPercent являются «связанными полями» в том смысле, что любые изменения в этих полях будут вызывать объект поддержки изменения свойств для отправки уведомления всем слушателям, которые зарегистрировались в объекте поддержки., как это отражено в методах public void setXxxx(...).

Таким образом, контроллер может зарегистрировать прослушиватели в модели для любых свойств, которые он хочет прослушивать, а затем уведомить представление о любых изменениях.Например:

class SpeedListener implements PropertyChangeListener {
    @Override
    public void propertyChange(PropertyChangeEvent evt) {
        float speed = model.getSpeed();
        view.setSpeed(speed);
    }
}

Установка может выглядеть примерно так:

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.GridLayout;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;

import javax.swing.*;
import javax.swing.event.SwingPropertyChangeSupport;

public class MVC2 {

    private static void createAndShowGui() {
        MvcModel model = new MvcModel();
        MvcView view = new MvcView();
        MvcController controller = new MvcController(model, view);
        controller.init();

        JFrame frame = new JFrame("MVC2");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.getContentPane().add(view.getMainDisplay());
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> createAndShowGui());
    }
}

class MvcView {
    private JPanel mainPanel = new JPanel();
    private JSlider speedSlider = new JSlider(0, MvcModel.MAX_SPEED);
    private JSlider batterySlider = new JSlider(0, 100);
    private JProgressBar speedBar = new JProgressBar(0, MvcModel.MAX_SPEED);
    private JProgressBar batteryPercentBar = new JProgressBar(0, 100);

    public MvcView() {
        speedSlider.setMajorTickSpacing(5);
        speedSlider.setMinorTickSpacing(1);
        speedSlider.setPaintTicks(true);
        speedSlider.setPaintLabels(true);
        speedSlider.setPaintTrack(true);

        batterySlider.setMajorTickSpacing(20);
        batterySlider.setMinorTickSpacing(5);
        batterySlider.setPaintTicks(true);
        batterySlider.setPaintLabels(true);
        batterySlider.setPaintTrack(true);

        speedBar.setStringPainted(true);
        batteryPercentBar.setStringPainted(true);

        JPanel inputPanel = new JPanel(new GridLayout(0, 1));
        inputPanel.add(createTitledPanel("Speed", speedSlider));
        inputPanel.add(createTitledPanel("Battery %", batterySlider));

        JPanel displayPanel = new JPanel(new GridLayout(0, 1));
        displayPanel.add(createTitledPanel("Speed", speedBar));
        displayPanel.add(createTitledPanel("Battery %", batteryPercentBar));

        mainPanel.setLayout(new GridLayout(1, 0));
        mainPanel.add(createTitledPanel("Input", inputPanel));
        mainPanel.add(createTitledPanel("Display", displayPanel));
    }

    private JComponent createTitledPanel(String title, JComponent component) {
        JPanel titledPanel = new JPanel(new BorderLayout());
        titledPanel.setBorder(BorderFactory.createTitledBorder(title));
        titledPanel.add(component);
        return titledPanel;
    }


    public JComponent getMainDisplay() {
        return mainPanel;
    }


    public void setSpeed(float speed) {
        speedBar.setValue((int) speed);
    }


    public void setBatteryPercent(int batteryPercent) {
        batteryPercentBar.setValue(batteryPercent);
    }


    public JSlider getSpeedSlider() {
        return speedSlider;
    }

    public JSlider getBatterySlider() {
        return batterySlider;
    }

}

class MvcController {
    private MvcModel model;
    private MvcView view;

    public MvcController(MvcModel model, MvcView view) {
        this.model = model;
        this.view = view;

        model.addPropertyChangeListener(MvcModel.BATTERY, new BatteryListener());
        model.addPropertyChangeListener(MvcModel.SPEED, new SpeedListener());

        view.getSpeedSlider().addChangeListener(chngEvent -> {
            int value = view.getSpeedSlider().getValue();
            model.setSpeed(value);
        });

        view.getBatterySlider().addChangeListener(chngEvent -> {
            int value = view.getBatterySlider().getValue();
            model.setBatteryPercentage(value);
        });
    }

    public void init() {
        view.getSpeedSlider().setValue(10);
        view.getBatterySlider().setValue(100);

        model.setSpeed(10);
        model.setBatteryPercentage(100);
    }

    class SpeedListener implements PropertyChangeListener {
        @Override
        public void propertyChange(PropertyChangeEvent evt) {
            float speed = model.getSpeed();
            view.setSpeed(speed);
        }
    }

    class BatteryListener implements PropertyChangeListener {
        @Override
        public void propertyChange(PropertyChangeEvent evt) {
            int batteryPercent = model.getBatteryPercentage();
            view.setBatteryPercent(batteryPercent);
        }
    }    
}

Примечание: Observer и Observable устарели в самой последней версии Java, поэтому следует избегать их использования.

1 голос
/ 20 апреля 2019

В реализации update метода вы можете определить с первым аргументом o, какой Observable изменился, а со вторым аргументом arg, какое значение изменилось при вызове: notifyObservers(this.speed);

Обратите внимание, что notifyObservers подпись принимает Object, а примитив float не является подклассом Object.

...