![](/img/trans.png)
[英]Java and GUI - Where do ActionListeners belong according to MVC pattern?
[英]According to MVC, where does this code belong?
考慮以下 GUI 屏幕:
左側是人員列表,右側是人員。 每當人員列表的選擇發生變化時,所選人員(名字和姓氏)必須顯示在右側。
我的問題是,下面的代碼屬於哪里? 什么會更“MVC”?
personListModel.addPropertyChangeListener("selection", e -> {
personModel.setFromDomainEntity(personListModel.getSelectedPerson().orElse(null));
});
完整的例子是這樣的:
public class MvcExample {
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
JFrame frame = new JFrame("Example");
frame.setLayout(new BorderLayout());
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
PersonListModel personListModel = new PersonListModel();
PersonListView listView = new PersonListView(personListModel);
frame.add(listView, BorderLayout.LINE_START);
PersonModel personModel = new PersonModel();
PersonView personView = new PersonView(personModel);
frame.add(personView, BorderLayout.CENTER);
personListModel.addPropertyChangeListener("selection", e -> {
personModel.setFromDomainEntity(personListModel.getSelectedPerson().orElse(null));
});
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
});
}
static class PersonModel {
private String firstName, lastName;
private SwingPropertyChangeSupport listeners;
public PersonModel() {
this.firstName = "";
this.lastName = "";
listeners = new SwingPropertyChangeSupport(this);
}
public void setFirstName(String firstName) {
if (firstName.equals(this.firstName))
return;
this.firstName = firstName;
listeners.firePropertyChange("firstname", null, null);
}
public void setLastName(String lastName) {
if (lastName.equals(this.lastName))
return;
this.lastName = lastName;
listeners.firePropertyChange("lastname", null, null);
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
public void setFromDomainEntity(Person person) {
setFirstName(person == null ? "" : person.firstName);
setLastName(person == null ? "" : person.lastName);
}
void addPropertyChangeListener(String property, PropertyChangeListener listener) {
listeners.addPropertyChangeListener(property, listener);
}
}
static class PersonView extends JPanel {
private PersonModel model;
private JTextField firstNameField = new JTextField(15);
private JTextField lastNameField = new JTextField(15);
public PersonView(PersonModel model) {
super(new FlowLayout());
setBorder(BorderFactory.createTitledBorder("Person View / Person Model"));
this.model = model;
firstNameField.setText(model.getFirstName());
lastNameField.setText(model.getLastName());
firstNameField.getDocument().addDocumentListener(new RunnableDocumentListener(() -> {
if (!model.getFirstName().equals(firstNameField.getText()))
model.setFirstName(firstNameField.getText());
}));
lastNameField.getDocument().addDocumentListener(new RunnableDocumentListener(() -> {
if (!model.getLastName().equals(lastNameField.getText()))
model.setLastName(lastNameField.getText());
}));
model.addPropertyChangeListener("firstname", e -> {
if (firstNameField.getText().equals(model.getFirstName()))
return;
firstNameField.setText(model.getFirstName());
});
model.addPropertyChangeListener("lastname", e -> {
if (lastNameField.getText().equals(model.getLastName()))
return;
lastNameField.setText(model.getLastName());
});
add(firstNameField);
add(lastNameField);
}
//@formatter:off
private static class RunnableDocumentListener implements DocumentListener{
private Runnable r;
public RunnableDocumentListener(Runnable r) {this.r = r;}
@Override
public void insertUpdate(DocumentEvent e) { this.r.run();}
@Override
public void removeUpdate(DocumentEvent e) { this.r.run();}
@Override
public void changedUpdate(DocumentEvent e) {this.r.run();}
}
//@formatter:on
}
static class PersonListView extends JPanel {
private JList<Person> personList;
private DefaultListModel<Person> listModel;
private PersonListModel model;
public PersonListView(PersonListModel model) {
super(new BorderLayout());
setPreferredSize(new Dimension(400, 400));
setBorder(BorderFactory.createTitledBorder("Person List View / Person List Model"));
this.model = model;
listModel = new DefaultListModel<>();
personList = new JList<>(listModel);
personList.setCellRenderer(new DefaultListCellRenderer() {
@Override
public Component getListCellRendererComponent(JList<?> list, Object value, int index,
boolean isSelected, boolean cellHasFocus) {
JLabel renderer = (JLabel) super.getListCellRendererComponent(list, value, index, isSelected,
cellHasFocus);
Person person = (Person) value;
renderer.setText(person.firstName + " - " + person.lastName);
return renderer;
}
});
syncData();
syncSelection();
model.addPropertyChangeListener("data", e -> {
syncData();
});
model.addPropertyChangeListener("selection", e -> {
syncSelection();
});
personList.getSelectionModel().addListSelectionListener(e -> {
model.setSelectedPerson(personList.getSelectedValue());
});
add(new JScrollPane(personList));
}
private void syncData() {
listModel.removeAllElements();
for (Person p : model.getPersons()) {
listModel.addElement(p);
}
}
private void syncSelection() {
if (personList.getSelectedValue() == model.getSelectedPerson().orElse(null))
return;
personList.setSelectedValue(model.getSelectedPerson(), true);
}
}
// Domain Entity
static class Person {
private String firstName, lastName;
public Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
}
static class PersonListModel {
private List<Person> persons;
private Person selectedPerson;
private SwingPropertyChangeSupport listeners;
public PersonListModel() {
persons = new ArrayList<>();
persons.add(new Person("Stack", "OverFlow"));
persons.add(new Person("Jackie", "Chan"));
persons.add(new Person("Something", "Else"));
listeners = new SwingPropertyChangeSupport(this);
}
public void setSelectedPerson(Person selectedPerson) {
if (this.selectedPerson == selectedPerson)
return;
this.selectedPerson = selectedPerson;
listeners.firePropertyChange("selection", null, null);
}
public Optional<Person> getSelectedPerson() {
return Optional.ofNullable(selectedPerson);
}
public List<Person> getPersons() {
return Collections.unmodifiableList(persons);
}
void addPropertyChangeListener(String property, PropertyChangeListener listener) {
listeners.addPropertyChangeListener(property, listener);
}
}
}
我的想法是我可以把它放在 4 個地方。
選項 #1:如示例所示。 父 VC 部分持有它。 有一個擴展 JFrame 並依賴於 PersonListView 和 PersonView 的 ApplicationFrame。 它增加了兩個模型之間的協調:
class ApplicationFrame extends JFrame {
public ApplicationFrame(PersonListView personListView, PersonView personView) {
//...
personListView.getModel().addPropertyChangeListener("selection",e->{
personView.getModel().setFromDomainEntity(personListModel.getSelectedPerson().orElse(null));
});
//...
}
}
這種方法似乎是合適的,但是如果有更多的模型需要像這樣進行協調,ApplicationFrame 最終會導致太多的代碼和太多的存儲庫。
選項 #2一個 model 依賴於另一個,並使調用顯式(或隱式):
public PersonListModel(PersonModel personModel) {
//...
}
public void setSelectedPerson(Person selectedPerson) {
if (this.selectedPerson == selectedPerson)
return;
this.selectedPerson = selectedPerson;
personModel.setFromDomainEntity(selectedPerson);
listeners.firePropertyChange("selection", null, null);
}
或者:
public PersonModel(PersonListModel listToObserve) {
//
listToObserve.addPropertyChangeListener("selection", e->{
setFromDomainEntity(listToObserve.getSelectedPerson().orElse(null));
});
}
現在,如果涉及更多模型,它們都會變得過於復雜。 這也會影響可測試性。
選項 #3:尊重 model 層次結構作為視圖 go。 PersonView 和 PersonListView 添加在(比如說)MainView 上。 所以很可能會有一個 MainModel。 而這個 MainModel 依賴於 PersonModel 和 PersonListModel。 所以代碼就在那里。 這將使項目處於一個很大的層次結構中,我認為很難調試或測試它。 另外,如果涉及更多模型,這個 MainModel 最終會是什么?
選項 #4:引入新的 class。 說諸如“PersonListToPersonCoordinator”之類的東西,它站在那里以在 2 個模型之間進行協調。 如果另一個 model 需要“聽到”列表中選定的人員,則引入一個新的 class 來做同樣的事情。 到目前為止,這對我來說是最好的選擇。 由於模型之間沒有 model 依賴關系,因此不存在復雜/長層次結構並且可測試性存在。
但是 MVC 對此有什么要說的呢?
我復制了你的 GUI。 我仍然不知道您所說的“觀察”一詞是什么意思。 Swing 中的所有觀察都發生在幕后。 你根本不需要處理觀察。
當您左鍵單擊JList
中的名稱時,該名稱將復制到表單中。
我創建了一個Person
class 和一個PersonsModel
class。 這兩個類都是普通的 Java getter / setter 類。 PersonsModel
class 是一個Person
實例List
。
事實證明,我可以通過一個方法調用將Person
實例List
復制到DefaultListModel
。
Person
和PersonsModel
是我的 model 類。 JListPersonGUI
是我的觀點 class。 PersonSelectionListener
是我的 controller class。 我的 controller class 甚至不需要引用 model,因為我將 Z20F35E630E630DAF539DFA8C3F68F5399D8CZ 復制到了DefaultListModel
。
這是完整的可運行代碼。 我把所有的類都做成了內部類,這樣我就可以將代碼作為一個塊發布。
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.util.ArrayList;
import java.util.List;
import javax.swing.BorderFactory;
import javax.swing.DefaultListModel;
import javax.swing.DefaultListSelectionModel;
import javax.swing.JFrame;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
public class JListPersonGUI implements Runnable {
public static void main(String[] args) {
SwingUtilities.invokeLater(new JListPersonGUI());
}
private JList<Person> personList;
private JTextField firstNameField;
private JTextField lastNameField;
private final PersonsModel model;
public JListPersonGUI() {
this.model = new PersonsModel();
}
@Override
public void run() {
JFrame frame = new JFrame("Example");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(createJListPanel(), BorderLayout.BEFORE_LINE_BEGINS);
frame.add(createPersonPanel(), BorderLayout.AFTER_LINE_ENDS);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
private JPanel createJListPanel() {
JPanel panel = new JPanel(new BorderLayout());
panel.setPreferredSize(new Dimension(400, 400));
panel.setBorder(BorderFactory.createTitledBorder(
"Person List View / Person List Model"));
DefaultListModel<Person> listModel = new DefaultListModel<>();
listModel.addAll(model.getPersons());
personList = new JList<>(listModel);
personList.addListSelectionListener(new PersonSelectionListener(this));
personList.setSelectionMode(DefaultListSelectionModel.SINGLE_SELECTION);
JScrollPane scrollPane = new JScrollPane(personList);
panel.add(scrollPane, BorderLayout.CENTER);
return panel;
}
private JPanel createPersonPanel() {
JPanel panel = new JPanel(new FlowLayout());
panel.setBorder(BorderFactory.createTitledBorder(
"Person View / Person Model"));
firstNameField = new JTextField(15);
panel.add(firstNameField);
lastNameField = new JTextField(15);
panel.add(lastNameField);
return panel;
}
public void updatePersonPanel(Person person) {
firstNameField.setText(person.getFirstName());
lastNameField.setText(person.getLastName());
}
public JList<Person> getPersonList() {
return personList;
}
public class PersonSelectionListener implements ListSelectionListener {
private final JListPersonGUI view;
public PersonSelectionListener(JListPersonGUI view) {
this.view = view;
}
@Override
public void valueChanged(ListSelectionEvent event) {
Person person = view.getPersonList().getSelectedValue();
view.updatePersonPanel(person);
}
}
public class PersonsModel {
private final List<Person> persons;
public PersonsModel() {
this.persons = new ArrayList<>();
this.persons.add(new Person("John", "Smith"));
this.persons.add(new Person("Sally", "Andrews"));
this.persons.add(new Person("Charles", "Barkley"));
}
public List<Person> getPersons() {
return persons;
}
}
public class Person {
private final String firstName, lastName;
public Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
@Override
public String toString() {
return firstName + " - " + lastName;
}
}
}
編輯添加:問題是,“視圖不應該觀察(觀察者模式)model 的變化嗎?”
Swing 中的答案是否定的。 在 GUI 中,更改是由於某種操作而發生的。 一個按鈕單擊,一個菜單選擇,一個 JList 選擇。 Swing 中的每個更改都會觸發某種偵聽器。 監聽器是 controller。 偵聽器處理對 model 的更改。
現在,在一個通用的 Java 應用程序中,您可以執行相同的操作。 您可以創建動作偵聽器作為對觸發器的響應。 我很難抽象地回答這個問題。 例如,您可以每 15 分鍾觸發一次數據庫讀取以檢查更新。
我剛剛完成了修改你的 GUI 以處理更多操作。 同樣,不需要觀察者。
這是完整的可運行代碼。 我修改了 model 類,視圖 class,並添加了一個 controller ZA2F2ED4F8EBC2CBB61C21A29DC4 的JButtons
。
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import javax.swing.BorderFactory;
import javax.swing.DefaultListModel;
import javax.swing.DefaultListSelectionModel;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
public class JListPersonGUI implements Runnable {
public static void main(String[] args) {
SwingUtilities.invokeLater(new JListPersonGUI());
}
private JList<Person> personList;
private JTextField firstNameField;
private JTextField lastNameField;
private Person selectedPerson;
private final PersonSelectionListener personSelectionListener;
private final PersonsModel model;
public JListPersonGUI() {
this.model = new PersonsModel();
this.personSelectionListener = new PersonSelectionListener(this);
}
@Override
public void run() {
JFrame frame = new JFrame("Example");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.add(createJListPanel(), BorderLayout.BEFORE_LINE_BEGINS);
frame.add(createPersonPanel(), BorderLayout.AFTER_LINE_ENDS);
frame.pack();
frame.setLocationByPlatform(true);
frame.setVisible(true);
}
private JPanel createJListPanel() {
JPanel panel = new JPanel(new BorderLayout());
panel.setPreferredSize(new Dimension(400, 400));
panel.setBorder(BorderFactory.createTitledBorder(
"Person List View / Person List Model"));
DefaultListModel<Person> listModel = new DefaultListModel<>();
listModel.addAll(model.getPersons());
personList = new JList<>(listModel);
personList.addListSelectionListener(personSelectionListener);
personList.setSelectionMode(DefaultListSelectionModel.SINGLE_SELECTION);
JScrollPane scrollPane = new JScrollPane(personList);
panel.add(scrollPane, BorderLayout.CENTER);
return panel;
}
private JPanel createPersonPanel() {
JPanel panel = new JPanel(new FlowLayout());
panel.setBorder(BorderFactory.createTitledBorder(
"Person View / Person Model"));
JPanel gridPanel = new JPanel(new GridBagLayout());
GridBagConstraints gbc = new GridBagConstraints();
gbc.anchor = GridBagConstraints.LINE_START;
gbc.fill = GridBagConstraints.HORIZONTAL;
gbc.insets = new Insets(5, 5, 5, 5);
gbc.gridwidth = 1;
gbc.weightx = 1.0;
gbc.gridx = 0;
gbc.gridy = 0;
JLabel label = new JLabel("First Name:");
gridPanel.add(label, gbc);
gbc.gridx++;
firstNameField = new JTextField(15);
gridPanel.add(firstNameField, gbc);
gbc.gridx = 0;
gbc.gridy++;
label = new JLabel("Last Name:");
gridPanel.add(label, gbc);
gbc.gridx++;
lastNameField = new JTextField(15);
gridPanel.add(lastNameField, gbc);
JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.LEADING, 5, 5));
ButtonListener listener = new ButtonListener(this, model);
JButton addButton = new JButton("Add Name");
addButton.addActionListener(listener);
buttonPanel.add(addButton);
JButton updateButton = new JButton("Update Name");
updateButton.addActionListener(listener);
buttonPanel.add(updateButton);
JButton deleteButton = new JButton("Delete Name");
deleteButton.addActionListener(listener);
buttonPanel.add(deleteButton);
Dimension ad = addButton.getPreferredSize();
Dimension ud = updateButton.getPreferredSize();
Dimension dd = deleteButton.getPreferredSize();
Dimension ld = calculateLargestDimension(ad, ud, dd);
addButton.setPreferredSize(ld);
updateButton.setPreferredSize(ld);
deleteButton.setPreferredSize(ld);
gbc.gridwidth = 2;
gbc.gridx = 0;
gbc.gridy++;
gridPanel.add(buttonPanel, gbc);
panel.add(gridPanel);
return panel;
}
private Dimension calculateLargestDimension(Dimension... dimensions) {
int width = 0;
int height = 0;
for (int index = 0; index < dimensions.length; index++) {
width = Math.max(width, dimensions[index].width + 10);
height = Math.max(height, dimensions[index].height);
}
return new Dimension(width, height);
}
public void updatePersonPanel(Person person) {
this.selectedPerson = person;
firstNameField.setText(person.getFirstName());
lastNameField.setText(person.getLastName());
}
public String getFirstName() {
return firstNameField.getText().trim();
}
public String getLastName() {
return lastNameField.getText().trim();
}
public Person getSelectedPerson() {
return selectedPerson;
}
public PersonSelectionListener getPersonSelectionListener() {
return personSelectionListener;
}
public JList<Person> getPersonList() {
return personList;
}
public class PersonSelectionListener implements ListSelectionListener {
private final JListPersonGUI view;
public PersonSelectionListener(JListPersonGUI view) {
this.view = view;
}
@Override
public void valueChanged(ListSelectionEvent event) {
Person person = view.getPersonList().getSelectedValue();
view.updatePersonPanel(person);
}
}
public class ButtonListener implements ActionListener {
private final JListPersonGUI view;
private final PersonsModel model;
public ButtonListener(JListPersonGUI view, PersonsModel model) {
this.view = view;
this.model = model;
}
@Override
public void actionPerformed(ActionEvent event) {
String action = event.getActionCommand();
Person person = view.getSelectedPerson();
switch (action) {
case "Add Name":
model.addPerson(new Person(view.getFirstName(), view.getLastName()));
break;
case "Update Name":
int index = model.getPersonIndex(person);
if (index >= 0) {
Person updatePerson = model.getPersons().get(index);
updatePerson.setFirstName(view.getFirstName());
updatePerson.setLastName(view.getLastName());
}
break;
case "Delete Name":
model.removePerson(person);
break;
}
personList.removeListSelectionListener(view.getPersonSelectionListener());
DefaultListModel<Person> listModel = new DefaultListModel<>();
listModel.addAll(model.getPersons());
view.getPersonList().setModel(listModel);
personList.addListSelectionListener(view.getPersonSelectionListener());
}
}
public class PersonsModel {
private final List<Person> persons;
public PersonsModel() {
this.persons = new ArrayList<>();
this.persons.add(new Person("John", "Smith"));
this.persons.add(new Person("Sally", "Andrews"));
this.persons.add(new Person("Charles", "Barkley"));
}
public void addPerson(Person person) {
this.persons.add(person);
}
public void removePerson(Person person) {
this.persons.remove(person);
}
public int getPersonIndex(Person person) {
for (int index = 0; index < persons.size(); index++) {
Person listPerson = persons.get(index);
if (listPerson.getFirstName().equals(person.getFirstName())
&& listPerson.getLastName().equals(person.getLastName())) {
return index;
}
}
return -1;
}
public List<Person> getPersons() {
return persons;
}
}
public class Person {
private String firstName, lastName;
public Person(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
@Override
public String toString() {
return firstName + " - " + lastName;
}
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.