有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

MVC上特定字段的java观察者模式

在MVC模式上,这是模型通知视图的最佳选项(如果这首先是正确的方法),其中,从模型存储的所有数据字段中,只有几个字段被更新。特别是当我们只想更新视图的特定字段时

我目前正在使用一个MVC模式与Observer/Subscriber(JavaSwing),如下所述:https://stackoverflow.com/a/6963529但是当模型更新时,它会更改视图中的所有内容。当调用update()函数时,无法确定模型中的哪个字段发生了更改,以便只更新视图中所需的字段

我读了这个主题:https://softwareengineering.stackexchange.com/a/359008,还有这个:https://stackoverflow.com/a/9815189,我认为它很有用,但对于后面的主题,我不太明白如何在变量(int、float等)上设置propertyChangeListener。也与此相关: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();
    }
}

所以我在模型上的代码是:

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(),视图被告知需要在特定字段上更新自己,而不是在每个“可更改”字段上更新自己(如txtFldBattery

我之所以要这样做,是因为在视图上,有字段每秒更新几次,而且因为我需要更新视图上的所有内容,一个不需要经常更新的JComboBox在尝试选择一个选项时会一直关闭


共 (2) 个答案

  1. # 1 楼答案

    update方法实现中,您可以使用第一个参数o确定哪个可观察对象发生了更改,使用第二个参数arg确定调用时哪个值发生了更改:notifyObservers(this.speed);

    注意,notifyObservers的签名接受Object,并且float原语不是Object的子类

  2. # 2 楼答案

    我将使用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字段都是“绑定字段”,因为对这些字段的任何更改都将触发property change support对象向已注册到该支持对象的任何侦听器发出通知消息,如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);
            }
        }    
    }
    

    旁注:Observable和Observable在最新版本的Java中已被弃用,因此应该避免使用它们