Java MVP视图到底有多愚蠢?

Java MVP视图到底有多愚蠢?,java,swing,mvp,Java,Swing,Mvp,我正在努力学习MVP,但有些东西让我迷路了;如果演示者将视图用作界面,则视图不能只是控件的简单呈现。想象一下,尝试编写一个打字练习游戏,其中单词随机生成到UI中,用户必须在单词从屏幕上落下时键入 因此,视图将有如下方法: public interface View { addWord(String word, double x, double y); // or possibly (Word word) moveWord(String word, double distance)

我正在努力学习MVP,但有些东西让我迷路了;如果演示者将视图用作界面,则视图不能只是控件的简单呈现。想象一下,尝试编写一个打字练习游戏,其中单词随机生成到UI中,用户必须在单词从屏幕上落下时键入

因此,视图将有如下方法:

public interface View {
    addWord(String word, double x, double y); // or possibly (Word word)
    moveWord(String word, double distance);
    removeWord(String word);
    setScore(int score);
    registerKeyListener(KeyListener listener);
    // other stuff
}
但最终视图必须负责创建自定义控件。这里省略了很多代码,但希望这足以说明我的意思。例如:

public class SwingView {
    private JPanel thePanel;

    private Map<String, WordComponent> currentWords = new HashMap<>();

    public SwingView() {
        thePanel = new JPanel(new WordLayout());
        // other stuff
    }

    public void addWord(String word, double x, double y) {
        WordComponent newWord = new WordComponent(word);
        currentWords.put(word, newWord);
        Point2D.Double point = new Point2D.Double(x, y);
        thePanel.add(newWord, point);
    }

    public void removeWord(String word) {
        WordComponent theWord = currentWords.get(theWord);
        thePanel.remove(theWord);
    }
}
公共类SwingView{
私人JPanel thePanel;
私有映射currentWords=newHashMap();
公众SwingView(){
thePanel=newjpanel(newwordlayout());
//其他东西
}
公共void addWord(字符串字,双x,双y){
WordComponent newWord=新单词component(word);
currentWords.put(单词、新词);
Point2D.Double point=新的Point2D.Double(x,y);
面板添加(新词、点);
}
public void removeWord(字符串字){
WordComponent theWord=currentWords.get(theWord);
面板。删除(单词);
}
}
视图实现已经有了逻辑。它正在维护其
WordComponent
s的
Map
。我在这里有两个自己的类,
WordLayout实现了LayoutManager2
,而
WordComponent扩展了JLabel
(或者其他一些,但这将是更多的代码)

理论上,演示者应该对Swing一无所知,所以我可以使用一个可能登录到控制台或其他地方的模拟进行单元测试。但简单地管理Swing对象本身就是一项工作。或者,如果我想把这个应用程序转换成一个Tomcat网页呢。现在class
ServletView
正在管理移动单词的AJAX调用。它依赖于AJAX框架,而AJAX框架将更多的工作转移到
视图中

概要:
View
实现是否应该具有管理其自身组件的“逻辑”

后续:我上面写的代码可能甚至没有响应,因为
模型
演示者
没有在事件调度线程上工作(或者,它们是,这可能更糟)。传递事件分派线程的显示更新的代码去哪里了?或者,
演示者是否应该在事件调度线程上

编辑:我刚想到一个主意。有一个特定于平台的子演示者,该演示者了解实现细节,如您是否正在使用Swing或其他东西


Edit2:根据@DuncanJones的回答,还有一个问题。想象一下,我想用逻辑来调整游戏的大小,并根据新的大小调整所有东西的大小。该逻辑是在
视图中还是在
演示者中?

视图组件必须包含足够的逻辑,以便向用户显示界面。根据所使用的框架,视图中可能有相当多的代码。重要的是确保业务逻辑在于演示者

关于您的二次查询,当视图调用所有Presenter方法时(在Swing的情况下),所有Presenter方法都将在EDT上调用。除非演讲者要求的操作是琐碎的,否则我会立即启动一个后台线程来完成这项工作。当使用
SwingUtilities.invokeLater()
完成时,该线程将更新视图

事实上,为了避免与Swing绑定,我倾向于将自己的
EventDispatcher
类传递给每个演示者。这是一个与
SwingUtilities
方法相同的接口。如果需要的话,我可以换一个班

旁注:这会使使用JUnit对Presenter进行单元测试变得困难,因为Presenter方法(和单元测试)将在后台线程完成之前完成。我倾向于为每个演示者构造一个负责运行后台线程的执行器。然后,在单元测试环境中,我传入一个特殊的
Executor
实现,它在同一线程上立即执行
run()
方法。这确保了单元测试是单线程的。例如:

public class SwingView {
    private JPanel thePanel;

    private Map<String, WordComponent> currentWords = new HashMap<>();

    public SwingView() {
        thePanel = new JPanel(new WordLayout());
        // other stuff
    }

    public void addWord(String word, double x, double y) {
        WordComponent newWord = new WordComponent(word);
        currentWords.put(word, newWord);
        Point2D.Double point = new Point2D.Double(x, y);
        thePanel.add(newWord, point);
    }

    public void removeWord(String word) {
        WordComponent theWord = currentWords.get(theWord);
        thePanel.remove(theWord);
    }
}

MVP模式的不同风格的要点是将业务逻辑与视图逻辑解耦。考虑到这一点,拥有一个包含大量显示逻辑的视图是完全可以的。不过,这里有一些警告:

不要将小部件用作数据存储。小部件的用途是显示内容或接收输入。演示者和模型负责保持状态。如果视图是有状态的,演示者应该知道并协调视图。例如,窗口大小在大多数情况下是视图关注的问题。但是,如果要将其持久化到某些用户设置文件,则必须涉及演示者

没有要求视图是一个单一的、单一的类。必须有一个对象实现演示者期望的接口,并且有人生成事件。例如,Swing使用TableModel之类的组件,其中可以放入用于指定数据表示的代码。此外,JavaFX和Android可以减少很多样板代码来设置视图


当然,事件必须在正确的线程中处理。您可以使用一个事件总线,它可以被告知做正确的事情,或者将ExecutorService传递到每个视图中。此外,演示者调用的每个方法都必须将工作卸载到框架的首选线程。最好不要让演讲者担心这个问题,否则就很难测试它。正确的做法取决于框架。

答案太棒了。对于单元测试的
ExecutorService
实现,您是否有现成的代码示例