MVC 上特定领域的观察者模式
Observer Pattern on MVC for specific fields
在 MVC 模式上,这是模型通知视图的最佳选择(如果这首先是正确的方法),其中,从模型存储的所有数据字段中,只有几个其中有更新。特别是当我们只想更新视图的特定字段时。
我目前正在使用具有 Observer/Subscriber (JAVA Swing) 的 MVC 模式,如下所述: 但是当模型更新时,它会更改视图中的所有内容 update()
函数被调用,无法确定模型中的哪个字段已更改以便仅更新视图中的必需字段。
我读了这个主题:https://softwareengineering.stackexchange.com/a/359008 and this as well: which I think it's usefull, but for the later, I can't understand very well how can I set a propertyChangeListener on a variale (int, float, etc). Also related to this:
Mainclass软件启动所在运行:
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()
被调用,视图被告知她需要在那个特定字段上更新自己,而不是每个 "changable" 字段(如 txtFldBattery
.
我想这样做是因为在视图上,有些字段每秒更新几次,而且因为我需要更新视图上的所有内容,所以 JComboBox
不需要经常更新,尝试select一个选项时一直关闭。
在您的 update
方法实现中,您可以使用第一个参数 o
确定哪个 Observable 已更改,并使用第二个参数 arg
确定哪个值在您调用时更改:notifyObservers(this.speed);
注意 notifyObservers 的签名接受 Object
,并且 float
原语不是 Object
的子类。
我会使用 SwingPropertyChangeSupport,让每个模型的状态字段成为一个 "bound property",这样每个状态字段都可以单独收听。
例如,假设您有一个如下所示的模型:
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);
}
}
速度和 batteryPercent 字段都是 "bound fields",因为对这些字段的任何更改都会触发 属性 更改支持对象,以向已注册支持的任何侦听器发送通知消息对象,如 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 的最新版本中已被弃用,因此应该避免使用它们。
在 MVC 模式上,这是模型通知视图的最佳选择(如果这首先是正确的方法),其中,从模型存储的所有数据字段中,只有几个其中有更新。特别是当我们只想更新视图的特定字段时。
我目前正在使用具有 Observer/Subscriber (JAVA Swing) 的 MVC 模式,如下所述: 但是当模型更新时,它会更改视图中的所有内容 update()
函数被调用,无法确定模型中的哪个字段已更改以便仅更新视图中的必需字段。
我读了这个主题:https://softwareengineering.stackexchange.com/a/359008 and this as well: which I think it's usefull, but for the later, I can't understand very well how can I set a propertyChangeListener on a variale (int, float, etc). Also related to this:
Mainclass软件启动所在运行:
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()
被调用,视图被告知她需要在那个特定字段上更新自己,而不是每个 "changable" 字段(如 txtFldBattery
.
我想这样做是因为在视图上,有些字段每秒更新几次,而且因为我需要更新视图上的所有内容,所以 JComboBox
不需要经常更新,尝试select一个选项时一直关闭。
在您的 update
方法实现中,您可以使用第一个参数 o
确定哪个 Observable 已更改,并使用第二个参数 arg
确定哪个值在您调用时更改:notifyObservers(this.speed);
注意 notifyObservers 的签名接受 Object
,并且 float
原语不是 Object
的子类。
我会使用 SwingPropertyChangeSupport,让每个模型的状态字段成为一个 "bound property",这样每个状态字段都可以单独收听。
例如,假设您有一个如下所示的模型:
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);
}
}
速度和 batteryPercent 字段都是 "bound fields",因为对这些字段的任何更改都会触发 属性 更改支持对象,以向已注册支持的任何侦听器发送通知消息对象,如 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 的最新版本中已被弃用,因此应该避免使用它们。