Как полностью отделить модель от View / Controller в Java Swing

10

Существует ли набор общепринятых руководящих принципов проектирования для отделения классов Model от классов View / Controller в приложении Java Swing? Я не настолько обеспокоен тем, что View / Controller ничего не знает о модели, как наоборот: я хотел бы спроектировать свою модель так, чтобы она ничего не знала в javax.swing. В идеале у него должен быть простой API, позволяющий ему управлять чем-то столь же примитивным, как CLI. Скорее всего, это должен быть «двигатель».

Передача событий GUI в модель не так уж и сложна - исполнители действий могут вызывать API модели. Но что делать, когда модель вносит изменения в собственное состояние, которые необходимо отразить обратно в GUI? Вот для чего нужно «слушать», но даже «быть услышанным» не совсем пассивно; это требует, чтобы модель знала о добавлении слушателя.

Конкретная проблема, которая заставила меня задуматься, связана с очередью файлов. Что касается графического интерфейса пользователя, то DefaultListModelпозади него JListесть некоторый графический интерфейс, позволяющий выбирать файлы из файловой системы и добавлять их в JList. Со стороны модели он хочет вытащить файлы из нижней части этой «очереди» (что приведет к их исчезновению из JList) и обработать их каким-либо образом. На самом деле код модели уже написан - в настоящее время он поддерживает ArrayList<File>и предоставляет открытый add(File)метод. Но я в растерянности относительно того, как заставить мою Модель работать с View / Controller без каких-либо серьезных, специфичных для Swing модификаций Модели.

Я очень плохо знаком с программированием на Java и GUI, до сих пор всегда занимался «пакетным» и «внутренним» программированием - поэтому я заинтересован в поддержании строгого разделения между моделью и пользовательским интерфейсом, если это возможно и если это возможно учиться.

Глава
источник
Кстати: в MVC модель должна быть отделена от вида и контроллера по умолчанию. Вы должны прочитать ваш учебник снова, я думаю, что вы не совсем поняли концепцию. И вы могли бы реализовать пользовательскую коллекцию, которая уведомляет, когда она изменяется, во многом как интерфейс INotifyCollectionChanged .NET.
Сокол
@Falcon: спасибо за ссылки. Не уверен, что я понимаю ваш комментарий, однако .. "модель должна быть отделена от вида и контроллера по умолчанию". Не могли бы вы перефразировать или уточнить?
гл

Ответы:

10

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

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

Чтобы ответить на ваш вопрос, на самом деле вы также хотите отделить контроллер от представления (чтобы вы могли использовать одну и ту же логику бизнес-правил как для приложения Swing, так и для приложения консоли). В примере Swing вы хотите отделить контроллер от JWindowи любого виджета в Swing. Способ, который я использовал (до использования реальных платформ), заключается в создании интерфейса для представления, которое использует контроллер:

public interface PersonView {
    void setPersons(Collection<Person> persons);
}

public class PersonController {

    private PersonView view;
    private PersonModel model;

    public PersonController(PersonView view, PersonModel model) {
        this.view = view;
        this.model = model;
    }
    // ... methods to affect the model etc. 
    // such as refreshing and sort:

    public void refresh() {
        this.view.setPersons(model.getAsList());
    }

    public void sortByName(boolean descending) {
       // do your sorting through the model.
       this.view.setPersons(model.getSortedByName());
    }

}

Для этого решения во время запуска необходимо зарегистрировать контроллер в представлении.

public class PersonWindow extends JWindow implements PersonView {

    PersonController controller;
    Model model;

    // ... Constructor etc.

    public void initialize() {
        this.controller = new PersonController(this, this.model);

        // do all the other swing stuff

        this.controller.refresh();
    }

    public void setPersons(Collection<Person> persons) {
        // TODO: set the JList (in case that's you are using) 
        // to use the given parameter
    }

}

Возможно, было бы неплохо создать IoC-контейнер, чтобы выполнить все настройки за вас.

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

public class PersonConsole implements PersonView {

    PersonController controller;
    Model model;

    public static void main(String[] args) {
        new PersonConsole().run();
    }

    public void run() {
        this.model = createModel();
        this.controller = new PersonController(this, this.model);

        this.controller.refresh();
    }

    public void setPersons(Collection<Person> persons) {
        // just output the collection to the console

        StringBuffer output = new StringBuffer();
        for(Person p : persons) {
            output.append(String.format("%s%n", p.getName()));
        }

        System.out.println(output);
    }

    public void createModel() {
        // TODO: create this.model
    }

    // this could be expanded with simple console menu with keyboard
    // input and other console specific stuff

}    

Самое интересное в том, как делать обработку событий. Я реализовал это, позволив представлению регистрироваться в контроллере с помощью интерфейса, это делается с помощью шаблона Observer (если вы используете .NET, вы бы вместо этого использовали обработчики событий). Вот пример простого «наблюдателя документа», который сигнализирует, когда документ был сохранен или загружен.

public interface DocumentObserver {
    void onDocumentSave(DocModel saved);
    void onDocumentLoad(DocModel loaded);
}

// in your controller you implement register/unregister methods
private List<DocumentObserver> observers;

// register observer in to the controller
public void addObserver(DocumentObserver o) {
    this.observers.add(o);
}

// unregisters observer from the controller
public void removeObserver(DocumentObserver o) {
    this.observers.remove(o);
}

public saveDoc() {
    DocModel model = model.save();
    for (DocumentObserver o : observers) {
        o.onDocumentSave(model);
    }
}

public loadDoc(String path) {
    DocModel model = model.load(path);
    for (DocumentObserver o : observers) {
        o.onDocumentLoad(model);
    }        
}

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

public class DocumentWindow extends JWindow 
        implements DocView, DocumentObserver {

    //... all swing stuff

    public void onDocumentSave(DocModel saved) {
        // No-op
    }

    public void onDocumentLoad(DocModel loaded) {
        // do what you need with the loaded model to the
        // swing components, or let the controller do it on
        // the view interface
    }

    // ...

}

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

Есть пара, о которых я могу думать из своей головы: Eclipse и Netbeans RCP.

Вам все еще нужно разработать контроллеры и модели для себя, но именно поэтому вы используете ORM. Примером будет Hibernate .

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

Spoike
источник
Спасибо, что нашли время, чтобы написать такой длинный ответ. На данный момент большая часть этого у меня над головой, но я уверен, что Википедия поможет. Я имею в использовании как Eclipse , и Netbeans, и наклонившись к последнему. Я не уверен, как отличить Контроллер от Представления там, но ваш предварительный пример наверху помогает.
Глава
0

Я считаю, что иногда нам нужно идти на компромисс

Как вы говорите, было бы замечательно, если бы мы могли неявно распространять уведомление об изменениях без явной инфраструктуры для наблюдаемого объекта. Для распространенных императивных языков, таких как Java, C #, C ++, их среда выполнения слишком легка, на данный момент, во всяком случае. Под этим я подразумеваю, что это не является частью спецификации языка в настоящее время.

В вашем конкретном случае, я не думаю, что это определенно плохо для определения / использования некоторого универсального интерфейса, такого как INotifyPropertyChanged (как в c #), так как он все равно не автоматически связывается с представлением - он просто говорит, что если я изменюсь, Я скажу тебе .

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

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