【发布时间】:2021-07-11 08:55:22
【问题描述】:
考虑以下 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。它增加了 2 个模型之间的协调性:
class ApplicationFrame extends JFrame {
public ApplicationFrame(PersonListView personListView, PersonView personView) {
//...
personListView.getModel().addPropertyChangeListener("selection",e->{
personView.getModel().setFromDomainEntity(personListModel.getSelectedPerson().orElse(null));
});
//...
}
}
这种方法似乎很合适,但是如果有更多的模型需要像这样进行协调,ApplicationFrame 最终会导致太多的代码和太多的存储库。
选项 #2 一个模型依赖于另一个模型并使调用显式(或隐式):
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:在视图中尊重模型层次结构。 PersonView 和 PersonListView 添加在(比如说)MainView 上。所以很可能会有一个 MainModel。而这个 MainModel 依赖于 PersonModel 和 PersonListModel。所以代码就在那里。这将使项目处于一个很大的层次结构中,我认为很难调试或测试它。另外,如果涉及更多模型,这个 MainModel 最终会是什么?
选项#4:引入一个新类。说诸如“PersonListToPersonCoordinator”之类的东西,它站在那里以在 2 个模型之间进行协调。如果另一个模型需要“听到”列表中选定的人,则引入一个新类“PersonListToTheOtherModelCoordinator”,它只是做同样的事情。到目前为止,这对我来说是最好的选择。由于模型之间没有模型依赖关系,因此不存在复杂/长层次结构并且可测试性存在。
但是 MVC 对此有什么要说的呢?
【问题讨论】:
-
您的应用程序模型应该是一个 Person 类(您的 PersonModel)和一个包含 Person 实例的 jave.util.List 的 PersonsModel 类。您可以将 Person 实例列表复制到 JList 模型中,并将 Person 字段/变量复制到人员视图中。 PersonsModel 和 Person 类都是普通的 Java getter / setter 类并构成模型。 JList 模型只保存 JList 的 Person 实例。
-
查看 Stack Overflow answer 以获得对 Swing 中 MVC 模式的更全面解释。
-
@GilbertLeBlanc 在我的示例中拥有 Person 和 PersonModel 对我来说也是有争议的。但是这些观点观察到了什么? Say PersonListView 观察你所说的 PersonsModel(我所说的 PersonListModel)关于人员数据和选择的内容。那么 PersonView 也会观察 PersonListModel 吗? And when selection changes it observes the selected Person (and probably de-observes the old selection? Also, where do domain rules go? Is (what you say) Person my domain model as well?
-
我不明白你说的“观点观察到什么”是什么意思。
-
@GilbertLeBlanc 对不起。我的问题是,PersonView 观察哪个模型? PersonListView 观察哪个模型?
标签: java swing user-interface model-view-controller observer-pattern