【问题标题】:Interpretation of an interface driven implementation of the observer pattern in JavaJava中观察者模式的接口驱动实现的解释
【发布时间】:2015-08-22 16:37:43
【问题描述】:

我正在研究用 Java 实现的观察者模式,我对教程实现的具体工作原理有些疑问。

我知道这种模式用于所有期望用户与 GUI 交互的情况(例如用户单击按钮)或事件在不可预测的时间发生的所有情况(如计时器、当计时器捕捉到一个事件时,必须处理)。

我知道在观察者模式中涉及两种不同类型的对象:

  1. 主题对象:即在不可预知的时间发生某事的对象(例如,可以随时单击的按钮用户)

  2. observer 对象(或 listener):在我看来,它包含了当某些东西变为 subject 对象(例如它处理我的按钮的点击)。这是真的还是我错过了什么?我对这种说法绝对不正确...我怀疑 observer 对象可能只需要监听 subject 的变化并对主题执行操作当这种变化发生时。例如:按钮(subject)被点击,观察者监听它并发现这个变化并对主题执行特定的操作(对主题对象执行特定的方法)。 p>

正确的解释是什么?观察者监听主体变化并执行观察者定义的操作还是执行主体定义的操作?

我知道要做到这一点,我可以将 subject 的引用传递给 observer(我认为执行的操作已定义到主题中。)但这是一种不好的做法,因为它涉及主体和观察者之间的一种紧密耦合。我知道存在界面驱动的解决方案,但我很难弄清楚。

本教程制作了一个表示使用 Swing 呈现 2 个按钮的视图的示例:

package com.caveofprogramming.designpatterns.demo1.view;

import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.JButton;
import javax.swing.JFrame;

import com.caveofprogramming.designpatterns.demo1.model.Model;

public class View extends JFrame implements ActionListener {

    private Model model;

    private JButton helloButton;        // SUBJECT OBJECT: l'oggetto su cui avviene l'evento
    private JButton goodbyeButton;      // SUBJECT OBJECT: l'oggetto su cui avviene l'evento

    public View(Model model) {
        super("MVC Demo");

        this.model = model;

        // Crea i 2 bottoni definiti sopra:
        helloButton = new JButton("Hello!");
        goodbyeButton = new JButton("Goodbye!");

        // Setta il layout manager da usare che stabilisce come sono posizionati gli elementi:
        setLayout(new GridBagLayout());

        GridBagConstraints gc = new GridBagConstraints();
        gc.anchor = GridBagConstraints.CENTER;
        gc.gridx=1;
        gc.gridy=1;
        gc.weightx=1;
        gc.weighty=1;
        gc.fill=GridBagConstraints.NONE;

        add(helloButton, gc);   // Aggiunge helloButton dentro il layout

        gc.anchor = GridBagConstraints.CENTER;
        gc.gridx=1;
        gc.gridy=2;
        gc.weightx=1;
        gc.weighty=1;
        gc.fill=GridBagConstraints.NONE;

        add(goodbyeButton, gc); // Aggiunge goodbyeButton dentro il layout

        helloButton.addActionListener(this);
        goodbyeButton.addActionListener(this);

        goodbyeButton.addActionListener(new ActionListener() {

            @Override
            public void actionPerformed(ActionEvent e) {
                System.out.println("Sorry to see you go.");
            }

        });

        setSize(600, 500);                          // Setta le dimensioni della finestra
        setDefaultCloseOperation(EXIT_ON_CLOSE);    // Setta il comportamento di cosa succede premendo il tasto X
        setVisible(true);                           // Setta la finestra come visibile
    }

    @Override
    public void actionPerformed(ActionEvent e) {

        JButton source = (JButton)e.getSource();

        if(source == helloButton) {
            System.out.println("Hello there!");
        }
        else {
            System.out.println("Some other button.");
        }

    }   

}

在这个例子中,我有 2 个按钮代表我的 subject 对象,它们是:

private JButton helloButton;        
private JButton goodbyeButton;

View类实现了一个ActionListener接口(是不是前面提到的接口?)

查看 ActionListener.class 我发现:

// Method descriptor #8 (Ljava/awt/event/ActionEvent;)V
public abstract void actionPerformed(java.awt.event.ActionEvent arg0);

这是一个必须实现的方法,这是一个动作发生时调用的方法,我必须实现它,这个:

@Override
public void actionPerformed(ActionEvent e) {

    JButton source = (JButton)e.getSource();

    if(source == helloButton) {
        System.out.println("Hello there!");
    }
    else {
        System.out.println("Some other button.");
    }

}

actionPerformed() 方法在 View 类中实现,因为它扩展了 ActionListener 接口并处理按钮的点击。

ActionListener 接口实现是我的observer 对象吗?这意味着 View* 类包含 **subject 对象(我的按钮),但它也是 observer/listener 实现?

要将 listernerobserver 对象)添加到按钮上:

helloButton.addActionListener(this);

我通过 this 因为包含 observer 的类与定义 subject 对象的类相同。对吗?

这是一个最佳实践,因为我将主体和观察者捆绑到一个类中,并且我没有通过观察者内部的主体引用派生的紧密耦合?

这是我的推理正确还是我遗漏了什么?

【问题讨论】:

  • 我已经看到这种模式被多次使用,而且几乎总是以眼泪告终。 :)
  • 检查,在侦听器中单击了哪个按钮是个坏主意。毕竟,您可以为两个按钮编写不同的侦听器以避免if。这不是很多工作:在这种情况下,匿名类、lambda 表达式和方法引用是您的朋友。

标签: java swing jakarta-ee design-patterns observer-pattern


【解决方案1】:

让我们从“理论”背景开始。首先,必须了解观察者模式;以及“Java”通常如何“为您”提供它已经存在了“相当长的一段时间”。后来发生了微妙的变化,但我认为我们谈论的是九十年代后期的概念是公平的。

我的建议是:不要只阅读这种模式的初始形式(如著名的四人组“设计原则”一书中所述);还请查看Reactive Programming 对此有何评论。

所以,我的意思是:在 2015 年,您仍然可以像 10 或 15 年前那样坐下来开发 Java Swing 应用程序。但尽量不要太专注于这种做事方式;现在有替代品;并且根据您的应用程序的要求,“老式 Java GUI 的东西”可能不是正确的答案。

那么:您在最后几段中建议的那种“捆绑”更像是一种“反模式”,而不是最佳实践。当然,它适用于“非常小的一次写入永不更新”之类的组件。

所以,是的,当您开始学习观察者模式时;这样开始是很自然的事情。但这不是避免“紧密耦合”的答案 - 使用您的方法,耦合可以变得如此紧密(我们不是在谈论同一类的东西吗?!)几乎不可能“解耦”以后再说吧。

一旦您谈到“现实”的应用程序,您就应该避免这种捆绑。它始于这样一个事实……您可能希望在单击按钮时发生相同的“动作”……或者当用户转到某个菜单时。或者你的 UI 的其他部分变得更加复杂;突然之间,同一个按钮需要发生“不同”的动作;或者“这里的这个东西”的状态现在变得依赖于“那里的其他东西”的状态。

换句话说:如果您的应用程序很复杂,那么建议的方法会导致巨大的、单一的、不可维护的意大利面条式代码碗的变化非常大。

换句话说:除了“将来不需要维护的简单练习代码”之外的任何内容都不应该按照您在此处概述的模式构建。

是的,我承认这并没有回答隐含的问题:“如何才能做得更好”......因为这是一个艰难的问题。一个潜在的提示可能是来自 Oracle 的 JavaFx“best practices

【讨论】:

  • 您的回答超出了我的预期。现在我的疑问得到了解决,因为对我来说似乎很奇怪的是,在教程中谈到没有紧密耦合,然后将观察者放在包含主题的同一个类中......顺便说一下,这只是一个教程,我我正在从 0 开始研究模式,我认为这是一个很好的起点,但我会听从你的建议,在未来我还会研究其他更好的基本模式形式的实现
猜你喜欢
  • 1970-01-01
  • 2011-12-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-07-10
  • 2013-12-03
  • 2017-08-30
  • 2012-05-11
相关资源
最近更新 更多