设计模式
观察者模式
观察者模式指的是一系列对象之间存在一对多的依赖关系,一旦某个对象的状态改变,其余对象就会立刻接收到相应的通知。
抽象
一些对象当作可观察者,而另一些对象当作观察者。为了在文字上做一些区别,可以将可观察者
称为主题
。
解释
-
一对多的依赖关系不仅仅指一个
主题
可以有多个观察者
,还包括一个观察者
可以订阅多个主题
。 -
只有当
主题
的状态改变的时候,并且期望推送
通知的时候才会通知观察者
。这种期望推送
的设计用来避免频繁的推送消息给观察者。
自定义观察者模式
假设有一个书店,顾客可以订阅该书店,如果该书店有新书到了顾客就会收到通知。
这里,书店就是一个主题
,顾客就是观察者
。
为了遵循针对接口编程,而非实现
的设计原则,需要先设计两个接口Subject
,Observer
。其中,Subject
具有注册观察者,取消注册观察者,通知观察者的行为。Observer
具有利用主题推送的信息来更新自己的行为。
public interface Subject {
void registerObserver(Observer observer);
void unregisterObserver(Observer observer);
void notifyObserver(Observer observer);
}
public interface Observer {
void update(Object... args);
}
关于Observer#update()
方法需要说明的是你可以不使用可变参数列表,这里只是个人爱好。
接下来,需要定义一个书店来实现Subject
接口,一个中国消费者来实现Observer
接口。
public class BookStore implements Subject {
private static List<Observer> observerList = new ArrayList<>();
@Override
public void registerObserver(Observer observer) {
if (observerList != null) {
observerList.add(observer);
}
}
@Override
public void unregisterObserver(Observer observer) {
if (observerList != null) {
if (observerList.size() > 0) {
observerList.remove(observer);
}
}
}
@Override
public void notifyObserver(Observer observer) {
for (Observer o :
observerList) {
o.update("Thinking in Java","100");
}
}
}
public class ChineseCustomer implements Observer {
@Override
public void update(Object... args) {
String bookName = (String) args[0];
String bookPrice = (String) args[1];
System.out.println("received book.\nThe name is : " + bookName + "\n" + "The price is : " + bookPrice);
}
}
上述代码存在的问题
- 我们说一个
观察者
可以订阅多个主题
,但是ChineseCustomer#update()
方法限制了这种思想,因为它对任何主题
发送过来的信息都用唯一的方式来处理!
解决的办法就是对主题
的具体类型进行判断,不同类型采用不同的解析处理。在此处,可以让BookStore#notifyObserver()
把自身传递过来,这样,通过RTTI
就可以知道是谁发送的信息了。当然,不同实现的解决方式在形式上有一些区别,我们会在java.util实现的观察者模式
中看到官方的处理方式。 - 另外,上述代码中一旦
主题
有任何状态变化都会立即推送给观察者
,这是极其不合理的!因为消费者不会在乎自己不喜欢的书籍的更新消息,比如:一位小学生通常不会在乎微积分习题册的更新,一位贫困的顾客不会买太贵的书籍。总之,推送要有限度!这种判断是否推送的逻辑在不同的场景下完全不同,可以很简单,也可以很复杂。在完善实现
一节我以书籍价格是否小于100
为标准来判断是否推送消息给顾客。 - 还存在的问题是硬编码了企图推送的消息与推送的对象。这些都在
完善实现
一节进行改进。
测试一下
public static void main(String[] args) {
Subject subject = new BookStore();
Observer o = new ChineseCustomer();
subject.registerObserver(o);
subject.notifyObserver(o);
}
received book.
The name is : Thinking in Java
The price is : 100
官方的实现
jdk的java.util
包中包含了Obserable
和Observer
两个文件。其中Obserable
是类,Observer
是接口。这个类导致了一些问题,因为使用它的话我们无法遵循针对接口编程
的原则。它还导致了一些其他问题,见下分析。
官方的实现思路是:所有主题
都继承Obserable
,同样可以添加/移除/通知观察者
。不过,如果想要把消息通知给观察者
,则必须先把主题
状态changed
设置为true
来表明主题
改变了。这其实有一些歧义,因为该设计的真实意图是主题
已经改变了并且需要通知给观察者
。观察者
的实现与自定义观察者模式
相同。
import java.util.Observable;
public class BookStore extends Observable {
@Override
protected synchronized void setChanged() {
super.setChanged();
}
}
public class ChineseCustomer implements Observer {
@Override
public void update(Observable o, Object arg) {
String subjectName = o.getClass().getName();
if (subjectName.equals("two.two.BookStore")) {
String[] info = (String[]) arg;
System.out.println("Received book that name is : " + info[0] + ",price is : " + info[1]);
}
}
}
public static void main(String[] args) {
/*Observable subject = new BookStore();
Observer o = new ChineseCustomer();
subject.addObserver(o);
subject.notifyObservers();*/
BookStore subject = new BookStore();
Observer o = new ChineseCustomer();
subject.addObserver(o);
subject.setChanged();
subject.notifyObservers(new String[]{"C#","50"});
}
Received book that name is : C#,price is : 50
可以看到,必须覆盖Obserable#setChanged()
,因为它的访问权限是protected
!否则,你永远无法真正地通知到观察者
。我十分不解这个访问权限的设置。
但是,总之这种对Observable
的扩展导致我们不能针对接口编程
了(从注释部分的代码可以看到),即每一次都要实现一个具体的主题
,这不利于复用
。不过,站在jdk开发者的角度,我大概可以理解它们的设计!因为,他/她们可以说“我又不知道你具体想要哪种实现,你自己决定就好!”。
完善实现
定义一个书籍类来代表书籍信息,以info
来代表需要传递的信息。
@Override
public void notifyObserver(Object info) {
if ((((Book) info).getPrice()) >= 100) {
return;
}
for (Observer o :
observerList) {
o.update(info);
}
}
public static void main(String[] args) {
Subject subject = new BookStore();
Observer o = new ChineseCustomer();
subject.registerObserver(o);
Book book = new Book("C Primer Plus" ,120.50f);
subject.notifyObserver(book);
book = new Book("C++" ,39.90f);
subject.notifyObserver(book);
}
received book.
The name is : C++
The price is : 39.9
当然,上述实现还是不够完美,需要一些细节上的改善,例如仿照官方对是否推送的实现思路,但是允许针对接口
。
最后,还要说一下松耦合
。观察者设计模式的实现中各个对象之间是松耦合
的,因为可观察者
和观察者
都可以独立存在,当然也可以交互。这有利于保障程序的可复用性
。