簡體   English   中英

根據MVC,這段代碼屬於哪里?

[英]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 中的所有觀察都發生在幕后。 你根本不需要處理觀察。

示例 GUI

當您左鍵單擊JList中的名稱時,該名稱將復制到表單中。

我創建了一個Person class 和一個PersonsModel class。 這兩個類都是普通的 Java getter / setter 類。 PersonsModel class 是一個Person實例List

事實證明,我可以通過一個方法調用將Person實例List復制到DefaultListModel

PersonPersonsModel是我的 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 以處理更多操作。 同樣,不需要觀察者。

示例 2

這是完整的可運行代碼。 我修改了 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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM