观察者模式是JDK中使用最多的设计模式之一.
在学习观察者模式之前, 我在网上也找了很多博客, 这些博客都是的讲解都是基于 head first 设计模式 这本书的. 如果看书的话, 很简单就能学会这个设计模式, 但是将书上的内容些成博客, 其实是不太容易理解的. 所以在这篇博客中, 我将用我自己的方式以示例的方式来讲解这个设计模式.
1. 观察者模式是什么? 它解决了什么问题?
(1) 观察者模式是什么?
观察者模式由两个端组成, 分别是通知者(speaker)和观察者(listener). 观察者也可以称为监听者.
speaker和Listener是一对多的关系. 当speaker的状态发生改变时, 可以将这种改变通知给它所有的Listener.
(2) 解决了什么问题?
假如现在speaker里面做了某些操作后, 它需要将这些操作的结果(或者某些数据)告诉它所有的listener, 最容易想到的办法就是通过方法调用来实现, 但是这样就使得这多个listener和speaker之间形成紧耦合.
观察者模式就是为了解决这种一对多模式中的紧耦合问题.
可能你还不是很明白, 怎样解耦和, 那么没关系, 我们来举个栗子.
2. 一个小栗子学会观察者模式
(1) 问题描述
假设现在有这样一个需求, 现在有一个水果类Fruit, 这个类里面的属性分别为多种水果的价格, 现在有这几种水果: apple, banana, peach
现在有三个公司A/B/C, 水果店长期给这三个公司提供水果.
然而, 每天的水果的价格是不一样的, 所以水果店在每天早上都要告诉A/B/C今天水果的价格是什么.
上面这个简单的例子就形成了一个一对多的关系, Fruit VS A/B/C.
通常我们来解决这个问题会在A/B/C的实例对象中分别使用getter获取所需要的价格信息. 但这是在仅仅只有三个listener的情况下, 如果我们再把情况考虑复杂一点.
假设现在有十个公司要从水果店拿货, 并且现在水果店的水果种类有20种, 而这10个公司所需要的水果都各不一样, 难道你还要再在这10个公司的实例对象中逐一gettter吗? 那要是有100个公司, 1000种水果呢?
那么来看一下观察者模式是如何解决这个问题的.
(2) 问题解决
首先我要先说明两点:
- 1. 在Java中, 解耦通常都要借助接口, 所以观察者模式中, 也是根据接口来达到解耦的目的的;
- 2. 第二点可能我现在说你会觉得很懵逼, 但是你只需要知道这一点就好了. 在观察者模式中具体有两种模式: 推模式和拉模式. 我下面使用的这种模式属于拉模式.
好了, 下面来看解决方法.
(1) 基础类的设计
1. Fruit.java
public class Fruit {
private float apple;
private float banana;
private float peach;
public Fruit() { }
public float getApple() {
return apple;
}
public void setApple(float apple) {
this.apple = apple;
}
public float getBanana() {
return banana;
}
public void setBanana(float banana) {
this.banana = banana;
}
public float getPeach() {
return peach;
}
public void setPeach(float peach) {
this.peach = peach;
}
}
2. 各公司的类设计
public class ACompany {
private float apple;
public ACompany() { }
public float getApple() {
return apple;
}
public void setApple(float apple) {
this.apple = apple;
}
}
public class BCompany {
private float banana;
public BCompany() { }
public float getBanana() {
return banana;
}
public void setBanana(float banana) {
this.banana = banana;
}
}
public class CCompany {
private float peach;
public CCompany() { }
public float getPeach() {
return peach;
}
public void setPeach(float peach) {
this.peach = peach;
}
}
1. 接口的设计
观察者模式通常是由一对接口类构成的, 一个是speaker的接口, 另外一个是listener的接口.
1. listener的接口: IFruitListener.java
public interface IFruitListener {
void getPrice(Fruit fruit);
}
2. speaker的接口: IFruitSpeaker.java
public interface IFruitSpeaker {
void addListener(IFruitListener listener);
void removeListener(IFruitListener listener);
void sendPrice();
}
2. 使用观察者模式的水果店类和公司类
从上面的描述中, 很显然, 水果店类是”一”, 公司类是”多”. 所以在这个例子中, 水果店类就是speaker, 而各个公司类就是listener.
前面也说过, 观察者模式是通过接口来实现解耦的, 那么下来我们让水果店类和公司类分别来继承观察者模式的接口.
为了减少篇幅, 我省略了getter和setter方法.
1. speaker: Fruit.java
public class Fruit implements IFruitSpeaker {
private float apple;
private float banana;
private float peach;
// 保存所有Listener对象, 当需要通知监听者的时候, 就逐一遍历通知Listener对象
private List<IFruitListener> listeners;
public Fruit(float apple, float banana, float peach) {
this.apple = apple;
this.banana = banana;
this.peach = peach;
listeners = new ArrayList<>();
}
// 添加一个Listener到自己的监听列表里
@Override
public void addListener(IFruitListener listener) {
listeners.add(listener);
}
// 从自己的监听者列表里删除一个Listener
@Override
public void removeListener(IFruitListener listener) {
if(listeners.contains(listener)) {
listeners.remove(listener);
}
}
// 向所有的监听者发送消息
@Override
public void sendPrice() {
for (IFruitListener listener : listeners) {
listener.getPrice(this);
}
}
}
2. listener: ACompony
public class ACompany implements IFruitListener {
private float apple;
public ACompany() { }
// 实现IFruitListener接口的方法.
// 这里的实现是可以根据当前类的需要来自行实现. 比如A需要知道apple的价格, B需要知道banana的价格等.
@Override
public void getPrice(Fruit fruit) {
apple = fruit.getApple();
System.out.println("apple`s price: " + apple);
}
}
// B/C公司以此类推
下来我们来测试一下:
public class Test {
public static void main(String[] args) {
Fruit fruit = new Fruit(11, 22, 33);
ACompany aCompany = new ACompany();
BCompany bCompany = new BCompany();
CCompany cCompany = new CCompany();
aCompany.getPrice(fruit);
bCompany.getPrice(fruit);
cCompany.getPrice(fruit);
}
}
运行结果如下:
apple`s price: 11.0
banana`s price: 22.0
peach`s price: 33.0
前面提到过, 上面这种模式是”拉模式”, 还有一种是”推模式”. 那么什么是拉模式? 什么由是推模式呢?
(3) 拉模式和推模式
拉模式和推模式实质上是针对listener获取数据的方式而言的.
在上面的例子中, 我们可以很明确的看到, A/B/C三家公司所需要的水果的种类是不同的, 所以我们在给所有的监听者传送数据的时候, 并不是将具体的数据传送给它们, 而是将Fruit对象传送给它们, 让它们任意去”拉取”自己想要的数据, 这就是拉模式.
而推模式就是将由speaker指定的数据传送给liestener.
如果想要将上面的例子改为推模式, 大概改动的代码如下:
首先是listener接口的代码要进行改动:
public interface IFruitListener {
void getPrice(float apple, float banana, float peach);
}
其次是Fruit中的代码:
public class Fruit implements IFruitSpeaker {
private float apple;
private float banana;
private float peach;
private List<IFruitListener> listeners;
public Fruit(float apple, float banana, float peach) {
this.apple = apple;
this.banana = banana;
this.peach = peach;
listeners = new ArrayList<>();
}
@Override
public void addListener(IFruitListener listener) {
listeners.add(listener);
}
@Override
public void removeListener(IFruitListener listener) {
if(listeners.contains(listener)) {
listeners.remove(listener);
}
}
@Override
public void sendPrice() {
for (IFruitListener listener : listeners) {
// 这里有改动
listener.getPrice(apple, banana, peach);
}
}
}
那么与此同时, A/B/CCompony中的代码也要发生改变:
public class ACompany implements IFruitListener {
private float apple;
public ACompany() { }
// 这里有改动
@Override
public void getPrice(float apple, float banana, float peach) {
this.apple = apple;
System.out.println("apple`s price: " + apple);
}
}
关于观察者模式是使用拉模式还是推模式, 就看你具体面对什么样的情况了.
实质上, JDK里面也是内置了观察者模式了的, 作为speaker需要继承Observable类, 作为listener需要实现Observer接口. 但是因为speaker的实现只能通过继承Observable类来完成, 而Java又不支持多继承, 使得JDK内置的观察者模式的潜力受到了极大的限制. 所以关于JDK内置的观察者模式在这里我就不多介绍了, 感兴趣的小伙伴可以看看这方面的资料.
本文的刚一开始就提到, 观察者模式是JDK里面用到的最多的设计模式之一, 那么它都在哪里有出现过呢? 如果你有写过Swing的话, 那么你一定知道ActionListener, 它主要是用来监听按钮上所发生的动作. 这个ActionListener就使用了观察者模式. 如果你感兴趣的话, 可以去看看它的源码哦~
观察者模式的相关内容就是这些了. 我们经常强调代码要解耦, 观察者模式就解决了一对多的紧耦合问题, 同样我们也能通过观察者模式更好的体会在Java中要如何面向接口编程.