如何在Java Swing中将模型与视图/控制器完全分离


10

是否有一组通用的设计指南,用于在Java Swing应用程序中将Model类与View / Controller类分开?我不太担心View / Controller对模型一无所知,反之亦然:我想将模型设计为对javax.swing中的任何东西都不了解。理想情况下,它应该具有一个简单的API,使其能够由诸如CLI的原始语言驱动。从广义上讲,它应该是一个“引擎”。

将GUI事件传递给模型并不难-动作执行者可以调用模型的API。但是,当模型进行自己的状态更改并需要反映回GUI时,该怎么办?这就是“听”的目的,但是即使“听”也不是完全消极的。它要求模型知道有关添加侦听器的信息。

让我思考的特定问题涉及文件队列。在GUI端DefaultListModel后面有一个JList,还有一些GUI东西可以从文件系统中选择文件并将它们添加到JList中。在模型方面,它希望将文件从此“队列”的底部拉出(导致它们从JList中消失)并以某种方式进行处理。实际上,已经编写了Model代码-当前它维护ArrayList<File>并公开一个公共add(File)方法。但是我对如何使我的模型与视图/控制器一起使用而不对模型进行一些特定于Swing的大量修改感到困惑。

我对Java和GUI编程都非常陌生,到目前为止一直都在进行“批处理”和“后端”编程-因此,我有兴趣在模型和UI之间保持严格的划分,如果可能的话被教学。



顺便说一句:在MVC中,默认情况下应将模型与视图和控制器分离。您应该再次阅读教科书,我想您还不太了解这个概念。您可以实现一个自定义集合,该集合在发生更改时发出通知,就像.NET的INotifyCollectionChanged接口一样。
猎鹰

@Falcon:感谢您的链接。但是,不确定我是否理解您的评论。“默认情况下,模型应与视图和控制器分离”。您可以改写还是详细说明?

Answers:


10

没有针对MVC的公认的(即defacto)设计指南。自己做起来并不难,但需要对您的课程进行一些计划,并花费大量时间和耐心。

没有确定的解决方案的原因是因为有多种方法可以进行MVC,各有利弊。因此,要对此保持精明,然后做最适合自己的事情。

为了回答您的问题,您实际上实际上也希望将控制器与视图分离(以便您可以对Swing应用程序和控制台应用程序使用相同的业务规则逻辑)。在Swing示例中,您希望将控制器与JWindowSwing中的和其他小部件解耦。我以前做的方法(在使用实际框架之前)是为控制器使用的视图创建接口:

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框架来为您完成大部分工作,否则您最终将拥有大量的模板代码,而这些代码需要很长时间才能编写。您可以使用几个Rich Client Platform(RCP)来实现您最可能需要的一些基本功能,例如应用程序范围的文档处理和许多基本的事件处理。

我想到了几个:EclipseNetbeans RCP。

您仍然需要为自己开发控制器和模型,但这就是为什么要使用ORM的原因。例如Hibernate

IoC容器很酷,但是也有一些框架。例如Spring(除其他功能外,它还执行数据处理)。


感谢您抽出宝贵的时间写这么长的回复。在这一点上,大多数事情都让我有些头疼,但是我相信维基百科会有所帮助。我正在使用Eclipse和NetBeans和偏向于后者。我不确定如何从此处的“视图”中区分“控制器”,但是顶部的框架前示例会有所帮助。

0

我的看法是,有时我们需要妥协

如您所说,如果我们可以隐式传播变更通知,而无需观察对象具有显式基础结构的话,那就太好了。无论如何,对于Java,C#,C ++等常见命令式语言,它们的运行时体系结构都太轻巧。我的意思是目前它还不是语言规范的一部分。

在您的特定情况下,我认为定义/使用INotifyPropertyChanged之类的通用接口(在c#中)并不是一件坏事,因为它无论如何不会自动耦合到视图-它只是说如果我更改了,我会告诉你的

再次,我同意,如果我们不必自己定义更改通知,那将是很好的选择,但是如果所有类都隐含了更改通知,则可能会产生开销。


我想它的越多,我知道你是对的-模型对象参与到一些发起通知已发生变化的GUI程度; 否则,GUI将不得不轮询模型以发现更改-这很不好。
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.