Шаблон MVC и SWING

Один из шаблонов дизайна, которые мне кажутся наиболее трудными для получения реального понимания в "реальной жизни SWING", - это шаблон MVC. Я прошел через довольно много сообщений на этом сайте, которые обсуждают шаблон, но я все еще не чувствую, что у меня есть четкое представление о том, как использовать шаблон в моем (Java SWING) приложении.

Скажем, что у меня есть JFrame, который содержит таблицу, пару текстовых полей и несколько кнопок. Я бы, вероятно, использовал TableModel для "моста" JTable с базовой моделью данных. Тем не менее, все функции, ответственные за очистку полей, проверку полей, блокировки полей вместе с действиями кнопок, обычно идут непосредственно в JFrame. Однако не смешивает ли контроллер и представление шаблона?

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

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

Ответ 1

Книга, которую я очень рекомендую вам для MVC в качелях, будет Head First Design Patterns от Freeman и Freeman. У них есть очень подробное объяснение MVC.

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

  • Youre пользователь - вы взаимодействуете с вид. Контроллер берет ваш действий и интерпретирует их. если ты нажмите на кнопку, ее контролеров, чтобы выяснить, что это означает, и как должна быть модель на основе этого действия.
  • Контроллер запрашивает модель измените его состояние. Когда контроллер получает действие из представления, оно может потребовать, чтобы мнение изменилось как результат. Например, контроллер может включать или отключать определенные кнопок или пунктов меню в интерфейс.
  • Контроллер может также спросите мнение об изменении. Когда что-то изменения в модели, основанные либо на некоторые действия, которые вы предприняли (например, щелчок ) или некоторые другие внутренние изменения (например, следующая песня в плейлисте началось), модель уведомляет что его состояние изменилось.
  • The модель уведомляет мнение, когда его состояние изменилось.Когда что-то меняется в модели, основанный либо на некоторых действиях, которые вы предприняли (например, нажатие кнопки) или какой-либо другой внутреннее изменение (например, следующая песня в плейлист запустился), модель уведомляет мнение о том, что его состояние изменилось.
  • Вид запрашивает модель для состояния. В представлении отображается состояние отображается непосредственно из модели.Например, когда модель уведомляет представление о том, что началась новая песня воспроизведение, представление запрашивает песню имя из модели и отображает его. Представление может также спросить модель для состояние в результате контроллера запросив некоторые изменения в представлении.

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

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

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

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

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

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

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

Ответ 2

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

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

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

Пример кода

Вид:

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

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

Он идеально подходит для интерфейса трех для целей тестирования. Я только представил свою реализацию Model и Controller.

Модель:

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);
        ...
    }
}

Ответ 3

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

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

Пример

  • Red = model
  • Зеленый = просмотр
  • Синий = контроллер

enter image description here

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

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

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

Основы 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;
    }

}

Ответ 4

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

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

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

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


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

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

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

Это зависит от того, насколько сложна ваша проблема.

Ответ 5

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

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

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

Ответ 7

Если вы разработали программу с графическим интерфейсом , mvc pattern почти там, но размыто.

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

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

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

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

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