【问题标题】:According to MVC, where does this code belong?根据MVC,这段代码属于哪里?
【发布时间】: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


【解决方案1】:

我复制了你的 GUI。我仍然不知道您所说的“观察”一词是什么意思。 Swing 中的所有观察都是在幕后进行的。你根本不需要处理观察。

当您左键单击JList 中的名称时,该名称将复制到表单中。

我创建了一个Person 类和一个PersonsModel 类。这两个类都是普通的 Java getter / setter 类。 PersonsModel 类是 ListPerson 实例。

事实证明,我可以通过一个方法调用将Person 实例中的List 复制到DefaultListModel 中。

PersonPersonsModel 是我的模型类。 JListPersonGUI 是我的视图类。 PersonSelectionListener 是我的控制器类。我的控制器类甚至不需要引用模型,因为我将模型复制到了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;
        }
        
    }

}

编辑添加:问题是,“视图不应该观察(观察者模式)模型的变化吗?”

Swing 中的答案是否定的。在 GUI 中,更改是由于某种操作而发生的。一个按钮单击,一个菜单选择,一个 JList 选择。 Swing 中的每个更改都会触发某种监听器。侦听器是一个控制器。侦听器处理模型的更改。

现在,在一个通用的 Java 应用程序中,您可以做同样的事情。您可以创建动作侦听器作为对触发器的响应。我很难抽象地回答这个问题。例如,您可以每 15 分钟触发一次数据库读取以检查更新。

我刚刚完成了对您的 GUI 的修改以处理更多操作。同样,不需要观察者。

这是完整的可运行代码。我修改了模型类、视图类,并为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;
        }
        
    }

}

【讨论】:

  • 非常感谢您花时间重写我的 GUI。但是,根据 MVC,视图不应该观察(观察者模式)模型的变化吗?我创建的 GUI 与您的逻辑相同。选择发生,人员视图显示人员(表格已填写)。而这一切都没有“观察”的逻辑。让我说以下。 JList 是一个视图和控制器,并有一个 ListModel。它观察(监听更改)该模型。现在,我创建了具有 2 个子视图(2 个文本字段)的复合自定义视图。 PersonView 不应该有它的 PersonModel 并观察它吗? (同样,根据 MVC)。
  • 我基本上在谈论的是,(我的问题是基于),“超出小部件级 MVC”会发生什么?
  • @GeorgeZ.:这可能是因为我的 Swing 背景,但你可以为你能想象到的任何类型的动作编写动作监听器。我不擅长泛泛而谈,但一个具体的例子是编写一个触发器函数,每 15 分钟读取一次数据库。我不喜欢观察者的想法。请参阅我的更新答案。
  • 我想你误解了我的问题。 (或者我误解了 MVC 模式?)我的问题不是“如何在 GUI 中显示人”或与功能相关的问题。我的问题与 MVC 模式及其理论有关。你的回答没有回答我的问题,但我真的很感谢你花在这上面的时间。
猜你喜欢
  • 2012-03-15
  • 1970-01-01
  • 2014-12-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-12-03
相关资源
最近更新 更多