Bruce Eckel:OnJava 再聊设计模式之回调

本文作者Bruce Eckel深入探讨了设计模式中的回调概念,特别是观察者模式。他解释了观察者模式如何解耦代码和行为,以及在事件管理中的重要性。文中提供了一个花朵开放和关闭的示例,展示了如何动态注册和取消注册Observer以改变运行时的行为。此外,还提到了Java 9中弃用Observer和Observable,转而使用Flow类的情况。
摘要由CSDN通过智能技术生成

Bruce Eckel

读完需要

10

分钟

速读仅需 1 分钟

布鲁斯 • 埃克尔(Bruce Eckel),C++ 标准委员会的创始成员之一,知名技术顾问,专注于编程语言和软件系统设计方面的研究,常活跃于世界各大顶级技术研讨会。
他自 1986 年以来,累计出版 Thinking in C++、Thinking in Java、On Java 等十余部经典计算机著作,曾多次荣获 Jolt 最佳图书奖(被誉为“软件业界的奥斯卡”),其代表作 Thinking in Java 被译为中文、日文、俄文、意大利文、波兰文、韩文等十几种语言,在世界范围内产生了广泛影响。

Bruce Eckel:再聊设计模式(篇一)设计模式分类

Bruce Eckel:再聊设计模式(篇二)封装实现

Bruce Eckel:再聊设计模式(篇三)工厂模式

Bruce Eckel:再聊设计模式(篇四)函数对象模式

Bruce Eckel:再聊设计模式(篇五)改变接口

10

   

10 回调

回调可以将代码和行为解耦。这其中包含观察者模式(Observer),以及一类称为多路分发(Multiple Dispatching)的回调,其中包含《设计模式》中的访问者模式(Visitor)

11

   

观察者模式

观察者模式用于解决一个相当常见的问题:如果一组对象必须在某些其他对象改变状态时更新自身,该怎么做?这个场景可以在 SmallTalk 的“模型-视图-控制器”(model-view-controller, MVC) 设计中的“模型-视图”部分,或者在几乎完全相同的“文档-视图架构”(Document-View Architecture,可视为 MVC 的一种变体)中看到。假设你有一些数据(即“文档”)以及多个视图,比如一个绘图视图和一个文本视图。修改数据的时候,这两个视图都必须更新自身,而这时观察者模式就能派上用场。这是个比较常见的问题,因此相应的解决方案早就是标准 java.util 库的一部分了。

观察者模式使我们可以将调用的来源和被调用的代码以完全动态的方式解耦。正如同其他的回调形式,观察者模式包含一个钩子(hook)点,我们可以在此处改变代码。不同之处在于观察者模式那完全动态的本质。它常被用于处理“由其他对象的状态改变所触发的变更”的特殊场景,但它也是事件管理的基础。

观察者模式中实际上有两种“会改变的事物”:观察对象的数量和更新发生的方式。观察者模式使我们可以在不影响周围代码的情况下修改这两者。

Observer 是一个只有一个方法(update(Observable o, Object arg))的接口。被观察的对象(即 Observable)认为是时候更新所有的 Observer 时,便会调用该方法。第一个参数是导致更新发生的 Observable 对象,第二个参数提供了 Observer 更新自身时可能需要的任何额外信息。

一个 Observer 可以注册多个 Observable。

Observable 是决定何时及如何进行更新的“被观察的对象”。Observable 类会跟踪每个希望在变更发生时得到通知的 Observer。当发生任何应该让每个 Observer 更新自身的变更时,Observable 会调用自身的 notifyObservers()方法,该方法会调用每个 Observer 上的 update()方法。

Observable 中有个标志,用于表示它是否被改变。该标志允许我们一直等待,直到我们认为时机成熟,才会通知 Observer。标志状态的控制是 protected 的,因此只有继承者才能决定怎样才算是变更,而不是由此得出的 Observer 子类的终端用户。

大部分的工作是在 notifyObservers()中完成的。如果 changed 标志没有设置,则调用 notifyObservers()不会引发任何动作。否则该方法会首先清空 changed 标志,这样对 notifyObservers()的反复调用就不会浪费时间。该操作会在通知观察者之前完成,以防对 update()的调用引发对这个 Observable 对象的变更。然后它会调用每个 Observer 上的 update()方法。

一开始,似乎可以用一个普通的 Observable 对象来管理更新。但是这样做行不通,要达到效果,就必须继承 Observable,并在子类代码中的某处调用 setChanged()。该方法用于设置“changed”标志,意味着在调用 notifyObservers()的时候,所有的观察者都会被通知到。应该在何处调用 setChanged()取决于我们的程序逻辑。注意,Observer 和 Observable(Java 1.0 后已成为语言中的一部分)已在 Java 9 中被弃用,取而代之的是 Java 9 所引入的 Flow 类。

12

   

示例:观察花朵

这是一个观察者模式的示例:

// patterns/observer/ObservedFlower.java
// 观察者模式的示例
// {java patterns.observer.ObservedFlower}
package patterns.observer;
import java.util.*;
import java.util.function.*;

@SuppressWarnings("deprecation")
class Flower {
  private boolean isOpen = false;
  private boolean alreadyOpen = false;
  private boolean alreadyClosed = false;
  Observable opening = new Observable() {
    @Override public void notifyObservers() {
      if(isOpen && !alreadyOpen) {
        setChanged();
        super.notifyObservers();
        alreadyOpen = true;
      }
    }
  };
  Observable closing = new Observable() {
    @Override public void notifyObservers() {
      if(!isOpen && !alreadyClosed) {
        setChanged();
        super.notifyObservers();
        alreadyClosed = true;
      }
    }
  };
  public void open() { // 花瓣打开(开花)
    isOpen = true;
    opening.notifyObservers();
    alreadyClosed = false;
  }
  public void close() { // 花瓣闭合(合拢)
    isOpen = false;
    closing.notifyObservers();
    alreadyOpen = false;
  }
}

@SuppressWarnings("deprecation")
class Bee {
  private String id;
  Bee(String name)  { id = name; }
  // 观察开花:
  public final Observer whenOpened = (ob, a) ->
    System.out.println(
      "Bee " + id + "'s breakfast time!");
  // 观察合拢:
  public final Observer whenClosed = (ob, a) ->
    System.out.println(
      "Bee " + id + "'s bed time!");
}

@SuppressWarnings("deprecation")
class Hummingbird {
  private String id;
  Hummingbird(String name) { id = name; }
  public final Observer whenOpened = (ob, a) ->
    System.out.println("Hummingbird " +
      id + "'s breakfast time!");
  public final Observer whenClosed = (ob, a) ->
    System.out.println("Hummingbird " +
      id + "'s bed time!");
}

public class ObservedFlower {
  public static void main(String[] args) {
    Flower f = new Flower();
    Bee
      ba = new Bee("A"),
      bb = new Bee("B");
    Hummingbird
      ha = new Hummingbird("A"),
      hb = new Hummingbird("B");
    f.opening.addObserver(ha.whenOpened);
    f.opening.addObserver(hb.whenOpened);
    f.opening.addObserver(ba.whenOpened);
    f.opening.addObserver(bb.whenOpened);
    f.closing.addObserver(ha.whenClosed);
    f.closing.addObserver(hb.whenClosed);
    f.closing.addObserver(ba.whenClosed);
    f.closing.addObserver(bb.whenClosed);
    // 蜂鸟B决定睡觉
    // 移除whenOpened会停止开花的更新操作
    f.opening.deleteObserver(hb.whenOpened);
    // 一个会引起观察者注意的变更:
    f.open();
    f.open(); // 没有效果:花瓣已经打开
    System.out.println("---------------");
    // Bee A doesn't want to go to bed.
    // Removing whenClosed stops close updates.
    // 蜜蜂A不想睡觉
    // 移除whenClosed会停止合拢的更新操作
    f.closing.deleteObserver(ba.whenClosed);
    f.close();
    System.out.println("+++++++++++++++");
    f.close(); // 没有效果:花瓣已经闭合
    System.out.println("===============");
    f.opening.deleteObservers();
    f.open(); // 没有观察者会更新
    System.out.println("###############");
    f.close(); // 合拢的观察者仍然在这里
  }
}
/* 输出:
Bee B's breakfast time!
Bee A's breakfast time!
Hummingbird A's breakfast time!
---------------
Bee B's bed time!
Hummingbird B's bed time!
Hummingbird A's bed time!
+++++++++++++++
===============
###############
Bee B's bed time!
Hummingbird B's bed time!
Hummingbird A's bed time!
*/

Flower(花)可以 open()(开花)或 close()(闭拢)。通过匿名内部类,这两个事件成为可被分别独立观察的现象。opening 和 closing 都继承自 Observable,因此它们可以访问 setChanged()。

Observer 是基础卷第 13 章中介绍过的函数式接口(也称为 SAM 类型),因此 Bee 和 Hummingbird 中的 whenOpened 和 whenClosed 是使用 lambda 表达式定义的。whenOpened 和 whenClosed 各自独立观察着 Flower 的开花和闭拢。

在 main()中可以看到观察者模式一个主要的好处:通过 Observable 动态的注册/取消注册 Observer 来获得在运行时改变行为的能力。

注意,我们可以创建完全不同的其他观察对象,Observer 唯一和 Flower 有关联的地方就是 Observer 接口。

13

   

一个可视化的观察者示例

下面这个示例通过使用 Swing 库创建各种图形来达到效果。Swing 在本书中并未介绍。有一些方框被放置在屏幕上的网格中,每个方框都被初始化为随机颜色。另外,每个方框都 implements(实现)了 Observer 接口,并注册了一个 Observable 对象。当你单击一个方框时,所有其他的方框都会得到通知,这是因为 Observable 对象会自动调用每个 Observer 对象的 update()方法。在该方法内部,方框会检查其是否和被单击的方框相邻,如果相邻,则会改变自身的颜色,以匹配被单击的方框。

java.awt.event 库中有一个带有多个方法的 MouseListener 类,但是我们只对 mouseClicked()方法感兴趣。如果只是想实现 mouseClicked(),则无法通过编写 lambda 表达式实现,这是因为 MouseListener 有多个方法,所以它并不是函数式接口。Java 9 允许我们通过 default 关键字简化代码,以创建一个辅助接口,从而解决这个问题:

// onjava/MouseClick.java
// Helper interface to allow lambda expressions.
// 用于支持lambda表达式的辅助接口
package onjava;
import java.awt.event.*;

// Default everything except mouseClicked():
// 将一切都设为default,除了mouseClicked():
public interface MouseClick extends MouseListener {
    @Override
    default void mouseEntered(MouseEvent e) {}
    @Override
    default void mouseExited(MouseEvent e) {}
    @Override
    default void mousePressed(MouseEvent e) {}
    @Override
    default void mouseReleased(MouseEvent e) {}
}

现在可以成功地将 lambda 表达式转型为 MouseClick,并将其传给 addMouseListener()。

// patterns/BoxObserver.java
// 用Java内建的Observer类演示观察者模式
// {ExcludeFromGradle} // 不适用于WSL2
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import onjava.*;
import onjava.MouseClick;

@SuppressWarnings("deprecation")
class Boxes extends JFrame {
  Observable notifier = new Observable() {
    @Override
    public void notifyObservers(Object b) {
      setChanged();
      super.notifyObservers(b);
    }
  };
  public Boxes(int grid) {
    setTitle("Demonstrates Observer pattern");
    Container cp = getContentPane();
    cp.setLayout(new GridLayout(grid, grid));
    for(int x = 0; x < grid; x++)
      for(int y = 0; y < grid; y++)
        cp.add(new Box(x, y, notifier));
    setSize(500, 400);
    setVisible(true);
    setDefaultCloseOperation(DISPOSE_ON_CLOSE);
  }
}

@SuppressWarnings("deprecation")
class Box extends JPanel implements Observer {
  int x, y; // 网格中的位置
  Color color = COLORS[
    (int)(Math.random() * COLORS.length)
  ];
  static final Color[] COLORS = {
    Color.black, Color.blue, Color.cyan,
    Color.darkGray, Color.gray, Color.green,
    Color.lightGray, Color.magenta,
    Color.orange, Color.pink, Color.red,
    Color.white, Color.yellow
  };
  Box(int x, int y, Observable notifier) {
    this.x = x;
    this.y = y;
    notifier.addObserver(this);
    addMouseListener((MouseClick)
      e -> notifier.notifyObservers(Box.this));
  }
  @Override public void paintComponent(Graphics g) {
    super.paintComponent(g);
    g.setColor(color);
    Dimension s = getSize();
    g.fillRect(0, 0, s.width, s.height);
  }
  @Override
  public void update(Observable o, Object arg) {
    Box clicked = (Box)arg;
    if(nextTo(clicked)) {
      color = clicked.color;
      repaint();
    }
  }
  private boolean nextTo(Box b) {
    return Math.abs(x - b.x) <= 1 &&
           Math.abs(y - b.y) <= 1;
  }
}

public class BoxObserver {
  public static void main(String[] args) {
    int grid = 8;
    if(args.length > 0)
      grid = Integer.parseInt(args[0]);
    new Boxes(grid);
  }
}

当你第一眼看到Observable的在线文档时,会觉得有点困惑,因为似乎可以用一个普通的Observable对象来管理更新操作。但这样做是行不通的,如果想实现这种效果,就必须继承自Observable,然后在子类代码中的某个地方调用setChanged(),如你在Boxes.notifier中所见。setChanged()设置了“changed”标志,这意味着在调用notifyObservers()的时候,所有的观察者都会得到通知。本例中的setChanged()是在notifyObservers()中调用的,但你可以使用任何标准来决定何时调用setChanged()。

Boxes包含一个名为notifier的Observable对象,并且每创建出一个Box,都会将该Box绑定到notifier上。在Box中,不论你何时单击鼠标,notifyObservers()都会被调用,并将被单击的对象作为参数传递,因此所有(在自身的update()方法中)收到消息的方框都能知道谁被单击了,并能决定是否要改变自身的颜色。通过将notifyObservers()和update()中的代码组合使用,就能实现某些相当复杂的方案。


079cb61fe8589047ae34a4e1d878519c.png

f318027af0de3f3d97226209b583fd58.jpeg

本书特色

  • 查漏宝典:涵盖Java关键特性的设计原理和应用方法

  • 避坑指南:以产业实践的得失为鉴,指明Java开发者不可不知的设计陷阱

  • 经典普适:值得不同层次的Java开发者反复研读

  • 专家领读:4位一线业务专家、知名作译者帮你拆解书中难点,总结Java开发精要

值得一提的是,为了帮助新手加深理解,出版方邀请了4位从业10年以上知名作译者DDD 专家张逸、服务端专家梁桂钊、软件系统架构专家王前明、译者陈德伟)为本书录制【精讲视频】和【导读指南】,该视频已在B站和图灵社区发布,感兴趣的朋友可以去看看。

07df8bf8de65a0b05208e464ecbeb1ea.png

往期推荐

Bruce Eckel:再聊设计模式(篇一)

Bruce Eckel:再聊设计模式(篇二)封装实现

Bruce Eckel:再聊设计模式(篇三)工厂模式

Bruce Eckel:再聊设计模式(篇四)函数对象模式

Bruce Eckel - 详解函数式编程(卷一)

Bruce Eckel - 详解函数式编程(卷二)

Bruce Eckel - 详解函数式编程(卷三)

如何在 SpringBoot 项目中控制 RocketMQ消费线程数量

同事多线程使用不当导致OOM,被我怒怼了

怎样才能持续输出技术原创文章?

后Kubernetes时代的微服务

我,程序员,马上35岁...

史海峰:在时代节点上顺势而为是一种幸运

知明:技术 Leader 的思考法

一文理解分布式开发中的服务治理

聊聊 8 种架构模式

Google工程师是怎么写设计文档的?

聊聊技术人员如何做好团队管理

为什么大数据平台要回归 SQL

如葑:阿里云原生网关Envoy Gateway实践

如何用研发效能搞垮一个团队

他教全世界程序员怎么写好代码,答案写在这里!

研发效能提升的实践框架、模式与反模式

聊聊大中型公司都热衷于造轮子的故事

被滥用的“架构师”!

构建健壮的分布式系统

阿里在云原生架构下的微服务选型和演进

c2fdaaedfad8f8fea4514fa3f08f4623.jpeg

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值