Шаблон MVC и качели


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

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

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

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

7 66

7 ответов:

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

Краткая Аннотация

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

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

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

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

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

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

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

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

EDIT #1:

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

EDIT #2:

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

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

моя идея MVC (который я в настоящее время работает с, до сих пор так хорошо) это:

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

Пример Кода

Вид :

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

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

идеально для того чтобы взаимодействовать 3 для целей тестирования. Я только предоставил свою реализацию модели и контроллера.

Модель :

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

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

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

пример

  • Red = model
  • зеленый = вид
  • синий = регулятор

enter image description here

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

в java swing есть некоторые компоненты, которые реализуют модель и контроллер, а также. Е. Г. элемент javax.качать.Действие. Он реализует модель пользовательского интерфейса (свойства: включение, маленький значок, имя и т. д.) и является контроллер, потому что он расширяет 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;
    }

}

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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