Шаблон MVC и Свинг - PullRequest
       25

Шаблон MVC и Свинг

72 голосов
/ 07 марта 2011

Один из шаблонов проектирования, который мне труднее всего понять в «реальной жизни Swing», - это шаблон MVC.Я прочитал довольно много постов на этом сайте, в которых обсуждается шаблон, но я все еще не чувствую, что у меня есть четкое понимание того, как использовать этот шаблон в моем приложении Java Swing.* Допустим, у меня есть JFrame, который содержит таблицу, пару текстовых полей и несколько кнопок.Я бы, вероятно, использовал TableModel, чтобы «связать» JTable с базовой моделью данных.Однако все функции, отвечающие за очистку полей, проверку полей, блокировку полей и действия кнопок, обычно выполняются непосредственно в JFrame.Однако разве это не смешивает Controller и View шаблона?

Насколько я вижу, мне удается "правильно" реализовать шаблон MVC, глядя на JTable (и модель)но все становится мутным, когда я смотрю на весь JFrame в целом.

Мне бы очень хотелось услышать, как другие поступают в этом отношении.Как вы поступите, когда вам нужно отобразить таблицу, пару полей и несколько кнопок для пользователя, используя шаблон MVC?

Ответы [ 7 ]

101 голосов
/ 07 марта 2011

Книгой, которую я бы настоятельно рекомендовал вам для свинга MVC, была бы «Образцы дизайна с головой» Фримена и Фримена. У них очень подробное объяснение MVC.

Краткое резюме

  1. Вы пользователь - вы взаимодействуете с представлением. Представление - это ваше окно в модель. Когда вы делаете что-то с видом (например, нажмите Кнопка воспроизведения), затем вид сообщает контроллеру, что вы сделали. Это работа контроллера, чтобы справиться с этим.

  2. Контроллер просит модель изменить свое состояние. Контроллер принимает ваши действия и интерпретирует их. Если вы нажмете на Кнопка, это работа контроллера, чтобы выяснить, что это значит и как следует манипулировать моделью на основе этого действия.

  3. Контроллер также может попросить изменить представление. Когда контроллер получает действие от представления, ему может потребоваться сообщить вид, чтобы изменить в результате. Например, контроллер может включить или отключить определенные кнопки или пункты меню в интерфейсе.

  4. Модель уведомляет представление об изменении его состояния. Когда что-то меняется в модели, основываясь на каком-либо действии, которое вы предприняли (например, нажатие кнопки) или другое внутреннее изменение (например, следующее песня в списке воспроизведения началась), модель уведомляет мнение о том, что его состояние изменилось.

  5. Представление запрашивает у модели состояние. Представление получает отображаемое состояние непосредственно из модели. Например, когда модель уведомляет представление о том, что новая песня начала играть, представление запрашивает название песни из модели и отображает его. Вид мог бы также спросите модель для состояния в результате контроллера запрашивая некоторые изменения в представлении.

enter image description here Источник (Если вам интересно, что такое «кремообразный контроллер», подумайте о печенье Oreo, где контроллер является кремовым центром, вид - верхнее печенье, а модель - нижнее печенье. )

Хм, если вам интересно, вы можете скачать довольно интересную песню о паттерне MVC с здесь !

Одна проблема, с которой вы можете столкнуться при программировании на Swing, заключается в объединении потоков SwingWorker и EventDispatch с шаблоном MVC. В зависимости от вашей программы вашему представлению или контроллеру может потребоваться расширить SwingWorker и переопределить метод doInBackground(), в котором размещена ресурсоемкая логика. Это можно легко объединить с типичным шаблоном MVC и типично для приложений Swing.

РЕДАКТИРОВАТЬ # 1 :

Кроме того, важно рассматривать MVC как своего рода смесь различных шаблонов. Например, ваша модель может быть реализована с использованием шаблона Observer (требуется, чтобы представление было зарегистрировано в качестве наблюдателя модели), в то время как ваш контроллер может использовать шаблон Strategy.

РЕДАКТИРОВАТЬ # 2 :

Я также хотел бы ответить конкретно на ваш вопрос. Вы должны отобразить кнопки таблицы и т. Д. В представлении, которое, очевидно, будет реализовывать ActionListener. В вашем методе actionPerformed() вы обнаруживаете событие и отправляете его связанному методу в контроллере (помните - представление содержит ссылку на контроллер). Таким образом, когда кнопка нажата, событие обнаруживается представлением, отправленным методу контроллера, контроллер может напрямую попросить представление отключить кнопку или что-то в этом роде. Затем контроллер будет взаимодействовать с моделью и изменять ее (которая в основном будет содержать методы получения и установки, а также некоторые другие для регистрации и уведомления наблюдателей и т. Д.). Как только модель будет изменена, она вызовет обновление для зарегистрированных наблюдателей (это будет представлением в вашем случае). Следовательно, представление теперь обновится само.

33 голосов
/ 31 мая 2013

Мне не нравится идея о том, что представление уведомляется моделью при изменении данных.Я бы делегировал эту функциональность контроллеру.В этом случае, если вы измените логику приложения, вам не нужно вмешиваться в код представления.Задача представления только для компонентов приложения + компоновка не более, не более того.Разметка в Swing - это уже многословная задача, почему она мешает логике приложений?

Моя идея MVC (с которой я сейчас работаю, пока что хороша):

  1. Вид - самый тупой из трех.Он ничего не знает о контроллере и модели.Его заботит только простетика и расположение компонентов качания.
  2. Модель также тупая, но не такая тупая, как вид.Он выполняет следующие функции.
    • а.когда один из его установщиков вызывается контроллером, он отправляет уведомление своим слушателям / наблюдателям (как я уже сказал, я бы переназначил эту роль на контроллер).Я предпочитаю SwingPropertyChangeSupport для достижения этой цели, поскольку он уже оптимизирован для этой цели.
    • b.функциональность взаимодействия с базой данных.
  3. Очень умный контроллер.Знает вид и модель очень хорошо.Контроллер имеет две функции:
    • a.Он определяет действие, которое представление будет выполнять, когда пользователь взаимодействует с ним.
    • b.Слушает модель.Как я уже говорил, когда вызывается установщик модели, модель отправляет уведомление контроллеру.Задача контроллера - интерпретировать это уведомление.Возможно, потребуется отразить изменение в представлении.

Пример кода

Представление:

Как я уже говорил, создание представленияуже многословно, так что просто создайте свою собственную реализацию:)

interface View{
    JTextField getTxtFirstName();
    JTextField getTxtLastName();
    JTextField getTxtAddress();
}

Это идеальное решение для сопряжения трех в целях тестирования.Я только предоставил свою реализацию модели и контроллера.

Модель:

public class MyImplementationOfModel implements Model{
    ...
    private SwingPropertyChangeSupport propChangeFirer;
    private String address;
    private String firstName;
    private String lastName;

    public MyImplementationOfModel() {
        propChangeFirer = new SwingPropertyChangeSupport(this);
    }
    public void addListener(PropertyChangeListener prop) {
        propChangeFirer.addPropertyChangeListener(prop);
    }
    public void setAddress(String address){
        String oldVal = this.address;
        this.address = address;

        //after executing this, the controller will be notified that the new address has been set. Its then the controller's
        //task to decide what to do when the address in the model has changed. Ideally, the controller will update the view about this
        propChangeFirer.firePropertyChange("address", oldVal, address);
    }
    ...
    //some other setters for other properties & code for database interaction
    ...
}

Контроллер:

public class MyImplementationOfController implements PropertyChangeListener, Controller{

    private View view;
    private Model model;

    public MyImplementationOfController(View view, Model model){
        this.view = view;
        this.model = model;

        //register the controller as the listener of the model
        this.model.addListener(this);

        setUpViewEvents();
    }

    //code for setting the actions to be performed when the user interacts to the view.
    private void setUpViewEvents(){
        view.getBtnClear().setAction(new AbstractAction("Clear") { 
            @Override
            public void actionPerformed(ActionEvent arg0) {
                model.setFirstName("");
                model.setLastName("");
                model.setAddress("");
            }
        });

        view.getBtnSave().setAction(new AbstractAction("Save") { 
            @Override
            public void actionPerformed(ActionEvent arg0) {
                ...
                //validate etc.
                ...
                model.setFirstName(view.getTxtFName().getText());
                model.setLastName(view.getTxtLName().getText());
                model.setAddress(view.getTxtAddress().getText());
                model.save();
            }
        });
    }

    public void propertyChange(PropertyChangeEvent evt){
        String propName = evt.getPropertyName();
        Object newVal = evt.getNewValue();

        if("address".equalsIgnoreCase(propName)){
            view.getTxtAddress().setText((String)newVal);
        }
        //else  if property (name) that fired the change event is first name property
        //else  if property (name) that fired the change event is last name property
    }
}

Основной, где настраивается MVC:

public class Main{
    public static void main(String[] args){
        View view = new YourImplementationOfView();
        Model model = new MyImplementationOfModel();

        ...
        //create jframe
        //frame.add(view.getUI());
        ...

        //make sure the view and model is fully initialized before letting the controller control them.
        Controller controller = new MyImplementationOfController(view, model);

        ...
        //frame.setVisible(true);
        ...
    }
}
21 голосов
/ 22 июля 2013

Шаблон MVC - это модель структуры пользовательского интерфейса. Поэтому он определяет 3 элемента Модель, Представление, Контроллер:

  • Модель Модель - это абстракция чего-то, что представлено пользователю. В разгаре у вас есть дифференциация моделей графического интерфейса и моделей данных. Модели с графическим интерфейсом абстрагируют состояние компонента пользовательского интерфейса, например ButtonModel . Модели данных абстрагируют структурированные данные, которые пользовательский интерфейс представляет пользователю, например TableModel .
  • Представление Представление - это компонент пользовательского интерфейса, который отвечает за представление данных пользователю. Таким образом, он отвечает за все вопросы, связанные с пользовательским интерфейсом, такие как макет, рисование и т. Д. Например, JTable .
  • Контроллер Контроллер инкапсулирует код приложения, который выполняется для взаимодействия с пользователем (движение мыши, щелчок мыши, нажатие клавиши и т. Д.). Контроллерам может потребоваться ввод для их выполнения, и они производят вывод. Они читают свои данные из моделей и обновляют модели в результате выполнения. Они также могут реструктурировать пользовательский интерфейс (например, заменить компоненты пользовательского интерфейса или показать полностью новый вид). Однако они не должны знать о компонентах пользовательского интерфейса, потому что вы можете инкапсулировать реструктуризацию в отдельный интерфейс, который только вызывает контроллер. В свинге контроллер обычно реализуется с помощью ActionListener или Action .

Пример * ** 1029 тысяча двадцать-восемь * Красный = модель Зеленый = вид Синий = контроллер enter image description here

При нажатии Button вызывается ActionListener. ActionListener зависит только от других моделей. Некоторые модели используются в качестве входных данных, а другие - в качестве результата или вывода. Это как аргументы метода и возвращаемые значения. Модели уведомляют пользовательский интерфейс, когда они обновляются. Таким образом, логике контроллера не нужно знать компонент пользовательского интерфейса. Объекты модели не знают пользовательского интерфейса. Уведомление делается по схеме наблюдателя. Таким образом, объектам модели известно только, что есть кто-то, кто хочет получать уведомления об изменении модели.

В java swing есть некоторые компоненты, которые также реализуют модель и контроллер. Например. javax.swing.Action . Он реализует модель пользовательского интерфейса (свойства: включение, маленький значок, имя и т. Д.) И является контроллером, поскольку расширяет ActionListener .

A подробное объяснение, пример приложения и исходный код : https://www.link -intersystems.com / blog / 2013/07/20 / the-mvc-pattern-внедрен-with-java- свинг / .

Основы MVC менее чем в 240 строках:

public class Main {

    public static void main(String[] args) {
        JFrame mainFrame = new JFrame("MVC example");
        mainFrame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        mainFrame.setSize(640, 300);
        mainFrame.setLocationRelativeTo(null);

        PersonService personService = new PersonServiceMock();

        DefaultListModel searchResultListModel = new DefaultListModel();
        DefaultListSelectionModel searchResultSelectionModel = new DefaultListSelectionModel();
        searchResultSelectionModel
                .setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
        Document searchInput = new PlainDocument();

        PersonDetailsAction personDetailsAction = new PersonDetailsAction(
                searchResultSelectionModel, searchResultListModel);
        personDetailsAction.putValue(Action.NAME, "Person Details");

        Action searchPersonAction = new SearchPersonAction(searchInput,
                searchResultListModel, personService);
        searchPersonAction.putValue(Action.NAME, "Search");

        Container contentPane = mainFrame.getContentPane();

        JPanel searchInputPanel = new JPanel();
        searchInputPanel.setLayout(new BorderLayout());

        JTextField searchField = new JTextField(searchInput, null, 0);
        searchInputPanel.add(searchField, BorderLayout.CENTER);
        searchField.addActionListener(searchPersonAction);

        JButton searchButton = new JButton(searchPersonAction);
        searchInputPanel.add(searchButton, BorderLayout.EAST);

        JList searchResultList = new JList();
        searchResultList.setModel(searchResultListModel);
        searchResultList.setSelectionModel(searchResultSelectionModel);

        JPanel searchResultPanel = new JPanel();
        searchResultPanel.setLayout(new BorderLayout());
        JScrollPane scrollableSearchResult = new JScrollPane(searchResultList);
        searchResultPanel.add(scrollableSearchResult, BorderLayout.CENTER);

        JPanel selectionOptionsPanel = new JPanel();

        JButton showPersonDetailsButton = new JButton(personDetailsAction);
        selectionOptionsPanel.add(showPersonDetailsButton);

        contentPane.add(searchInputPanel, BorderLayout.NORTH);
        contentPane.add(searchResultPanel, BorderLayout.CENTER);
        contentPane.add(selectionOptionsPanel, BorderLayout.SOUTH);

        mainFrame.setVisible(true);
    }

}

class PersonDetailsAction extends AbstractAction {

    private static final long serialVersionUID = -8816163868526676625L;

    private ListSelectionModel personSelectionModel;
    private DefaultListModel personListModel;

    public PersonDetailsAction(ListSelectionModel personSelectionModel,
            DefaultListModel personListModel) {
        boolean unsupportedSelectionMode = personSelectionModel
                .getSelectionMode() != ListSelectionModel.SINGLE_SELECTION;
        if (unsupportedSelectionMode) {
            throw new IllegalArgumentException(
                    "PersonDetailAction can only handle single list selections. "
                            + "Please set the list selection mode to ListSelectionModel.SINGLE_SELECTION");
        }
        this.personSelectionModel = personSelectionModel;
        this.personListModel = personListModel;
        personSelectionModel
                .addListSelectionListener(new ListSelectionListener() {

                    public void valueChanged(ListSelectionEvent e) {
                        ListSelectionModel listSelectionModel = (ListSelectionModel) e
                                .getSource();
                        updateEnablement(listSelectionModel);
                    }
                });
        updateEnablement(personSelectionModel);
    }

    public void actionPerformed(ActionEvent e) {
        int selectionIndex = personSelectionModel.getMinSelectionIndex();
        PersonElementModel personElementModel = (PersonElementModel) personListModel
                .get(selectionIndex);

        Person person = personElementModel.getPerson();
        String personDetials = createPersonDetails(person);

        JOptionPane.showMessageDialog(null, personDetials);
    }

    private String createPersonDetails(Person person) {
        return person.getId() + ": " + person.getFirstName() + " "
                + person.getLastName();
    }

    private void updateEnablement(ListSelectionModel listSelectionModel) {
        boolean emptySelection = listSelectionModel.isSelectionEmpty();
        setEnabled(!emptySelection);
    }

}

class SearchPersonAction extends AbstractAction {

    private static final long serialVersionUID = 4083406832930707444L;

    private Document searchInput;
    private DefaultListModel searchResult;
    private PersonService personService;

    public SearchPersonAction(Document searchInput,
            DefaultListModel searchResult, PersonService personService) {
        this.searchInput = searchInput;
        this.searchResult = searchResult;
        this.personService = personService;
    }

    public void actionPerformed(ActionEvent e) {
        String searchString = getSearchString();

        List<Person> matchedPersons = personService.searchPersons(searchString);

        searchResult.clear();
        for (Person person : matchedPersons) {
            Object elementModel = new PersonElementModel(person);
            searchResult.addElement(elementModel);
        }
    }

    private String getSearchString() {
        try {
            return searchInput.getText(0, searchInput.getLength());
        } catch (BadLocationException e) {
            return null;
        }
    }

}

class PersonElementModel {

    private Person person;

    public PersonElementModel(Person person) {
        this.person = person;
    }

    public Person getPerson() {
        return person;
    }

    @Override
    public String toString() {
        return person.getFirstName() + ", " + person.getLastName();
    }
}

interface PersonService {

    List<Person> searchPersons(String searchString);
}

class Person {

    private int id;
    private String firstName;
    private String lastName;

    public Person(int id, String firstName, String lastName) {
        this.id = id;
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public int getId() {
        return id;
    }

    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return lastName;
    }

}

class PersonServiceMock implements PersonService {

    private List<Person> personDB;

    public PersonServiceMock() {
        personDB = new ArrayList<Person>();
        personDB.add(new Person(1, "Graham", "Parrish"));
        personDB.add(new Person(2, "Daniel", "Hendrix"));
        personDB.add(new Person(3, "Rachel", "Holman"));
        personDB.add(new Person(4, "Sarah", "Todd"));
        personDB.add(new Person(5, "Talon", "Wolf"));
        personDB.add(new Person(6, "Josephine", "Dunn"));
        personDB.add(new Person(7, "Benjamin", "Hebert"));
        personDB.add(new Person(8, "Lacota", "Browning "));
        personDB.add(new Person(9, "Sydney", "Ayers"));
        personDB.add(new Person(10, "Dustin", "Stephens"));
        personDB.add(new Person(11, "Cara", "Moss"));
        personDB.add(new Person(12, "Teegan", "Dillard"));
        personDB.add(new Person(13, "Dai", "Yates"));
        personDB.add(new Person(14, "Nora", "Garza"));
    }

    public List<Person> searchPersons(String searchString) {
        List<Person> matches = new ArrayList<Person>();

        if (searchString == null) {
            return matches;
        }

        for (Person person : personDB) {
            if (person.getFirstName().contains(searchString)
                    || person.getLastName().contains(searchString)) {
                matches.add(person);
            }

        }
        return matches;
    }

}
2 голосов
/ 07 марта 2011

Вы можете создать модель в отдельном простом Java-классе, а контроллер - в другом.

Тогда вы можете иметь компоненты Swing поверх этого.JTable будет одним из представлений (а табличная модель будет де-факто являться частью представления - оно будет переводиться только из "совместно используемой модели" в JTable).

Всякий раз, когда таблица редактируется, ее табличная модель сообщает «главному контроллеру» что-то обновить.Тем не менее, контроллер не должен ничего знать о таблице.Поэтому вызов должен выглядеть примерно так: updateCustomer(customer, newValue), а не updateCustomer(row, column, newValue).

Добавить интерфейс слушателя (наблюдателя) для совместно используемой модели.Некоторые компоненты (например, ваша таблица) могут реализовать это напрямую.Другим наблюдателем может быть контроллер, который координирует доступность кнопок и т. Д.


Это один из способов сделать это, но, конечно, вы можете упростить или расширить его, если это избыточно для вашего варианта использования.

Вы можете объединить контроллер с моделью и иметь те же обновления процесса класса и поддерживать доступность компонента.Вы даже можете сделать «разделяемую модель» TableModel (хотя, если она используется не только таблицей, я бы рекомендовал по крайней мере предоставить более дружественный API, который не пропускает абстракции таблицы)

С другой сторонысо стороны, вы можете иметь сложные интерфейсы для обновлений (CustomerUpdateListener, OrderItemListener, OrderCancellationListener) и выделенный контроллер (или посредник) только для координации различных представлений.

Это зависит от сложности вашей проблемы..

0 голосов
/ 11 июля 2016

Если вы разрабатываете программу с GUI , шаблон mvc почти есть, но размыт.

Отрезать код модели, вида и контроллера сложно, и обычноэто не только задача рефакторинга.

Вы знаете, что она у вас есть, когда ваш код можно использовать повторно.Если вы правильно внедрили MVC, должно быть легко реализовать TUI или CLI или RWD или мобильный первый дизайн ста же функциональность.Легко увидеть, что это сделано, а не сделать это на самом деле, более того, на существующем коде.

Фактически, взаимодействие между моделью, представлением и контроллером происходит с использованием других шаблонов изоляции (таких как Observer или Listener)

Я предполагаю, что этот пост объясняет это подробно, от прямого не-MVC шаблона (как вы будете делать на Q & D ) до окончательной многоразовой реализации:

http://www.austintek.com/mvc/

0 голосов
/ 14 марта 2011

Я нашел несколько интересных статей о реализации шаблонов MVC, которые могут решить вашу проблему.

0 голосов
/ 07 марта 2011

Для правильного разделения у вас обычно будет класс контроллера, которому делегируется класс Frame.Существуют различные способы установить отношения между классами - вы можете реализовать контроллер и расширить его классом основного представления или использовать отдельный класс контроллера, который Frame вызывает при возникновении событий.Представление обычно получает события от контроллера, реализуя интерфейс слушателя.

Иногда одна или несколько частей шаблона MVC являются тривиальными или настолько «тонкими», что добавляет ненужную сложность для их разделения.Если ваш контроллер полон однострочных вызовов, наличие его в отдельном классе может привести к запутыванию основного поведения.Например, если все события, которые вы обрабатываете, связаны с TableModel и являются простыми операциями добавления и удаления, вы можете выбрать реализацию всех функций управления таблицами в этой модели (а также обратные вызовы, необходимые для отображения вJTable).Это не настоящий MVC, но он избегает добавления сложности там, где это не нужно.

Как бы вы ни реализовывали его, помните в JavaDoc ваши классы, методы и пакеты, чтобы компоненты и их отношения были правильно описаны!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...