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


10

Чи існує колекція загально узгоджених інструкцій щодо дизайну для відділення класів Model від класів View / Controller у додатку Java Swing? Я не настільки стурбований тим, що View / Controller нічого не знає про модель, як навпаки: я б хотів розробити свою Модель, щоб не знати нічого в javax.swing. В ідеалі він повинен мати простий API, що дозволяє йому керувати чимось таким примітивним, як CLI. Це повинен бути, вільно кажучи, "двигун".

Комунікація подій GUI для моделі не надто складна - виконавці дій можуть викликати API моделі. Але як бути, коли модель вносить зміни у свій власний стан, які потребують відображення назад у графічному інтерфейсі? Ось для чого це "прослуховування", але навіть "слухання" не є зовсім пасивним; це вимагає, щоб модель знала про додавання слухача.

Конкретна проблема, яка змусила мене думати, передбачає чергу файлів. З боку GUI DefaultListModelстоїть позаду JList, і є деякі елементи GUI, щоб вибрати файли з файлової системи та додати їх до списку JList. Що стосується моделі, вона хоче витягнути файли з нижньої частини цієї "черги" (змусивши їх зникнути з JList) і якось обробити їх. Насправді, Модельний код уже написаний - він наразі підтримує ArrayList<File>та відкриває публічний add(File)метод. Але я в занепокоєнні щодо того, як змусити свою модель працювати з View / Controller без деяких важких модифікацій, характерних для Swing, для моделі.

Я дуже новачок як у програмі Java, так і в графічному інтерфейсі, до цього часу завжди робив "пакетне" та "бек-енд" програмування - отже, мій інтерес до суворого поділу між моделлю та інтерфейсом користувача, якщо це можливо і якщо це можливо вчити.



Btw: У 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. Прикладом може бути сплячий режим .

Контейнери IoC - це все круто, але для цього також є рамки. Наприклад, Spring (який також здійснює обробку даних, а також між іншим).


Дякуємо, що знайшли час, щоб написати таку довгу відповідь. Більшість це зараз трохи над головою, але я впевнений, що Вікіпедія допоможе. Я маю в використанні як Eclipse , і Netbeans, і нахилившись до останнього. Я не впевнений, як відрізнити контролер від View, але ваш попередній приклад вгорі допомагає.
Чап

0

Я вважаю, що іноді нам потрібно йти на компроміс

Як ви кажете, було б чудово, якби ми могли поширювати сповіщення про зміни неявно, без того, щоб спостережуваний об’єкт мав для цього явну інфраструктуру. Для загальноприйнятих загальнодоступних мов, таких як Java, C #, C ++, їхня архітектура виконання занадто легка, як і зараз. Маючи на увазі, я зараз не є частиною мовної специфікації.

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

Знову ж таки, я погоджуюся, що було б чудово, якби нам не довелося визначати сповіщення про зміни самостійно, але знову ж таки, якщо це було б явним для всіх класів, воно може мати накладні витрати.


чим більше я думаю про це, я розумію , що ти маєш рацію - об'єктна модель має брати участь в якій - то ступеня , в ініціюванні повідомлення GUI , що зміна відбулася; інакше GUI повинен був опитати модель, щоб виявити зміни - і це погано.
Чап
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.