观察者模式说明
观察者模式是我们在开发中使用得非常频繁的一种设计模式,它比较好的解决了在开发中对象之间的联动关系,即一个对象的状态或者特性改变后,其它一个或者多个对象需要随之改变的联动关系。
观察者分为两种模型,一种是推模型,一种是拉模型。推模型主要是当被观察者发生改变时,主动的推送观察者感兴趣的具体东西;而拉模型同样的也是推给观察者,但不同的是,此时被观察者并不知道观察者对什么东西感兴趣,所以将自己本身作为一个对象传递给了观察者,然后让观察者自己去拉感兴趣的东西。
同时,java也给我们定义好了观察者与被观察者的接口,我们只需要对应的实现接口就可以 实现观察者模式。推模型和拉模型都可以通过此接口来实现。
观察者模式uml图
Observer: 观察者接口,主要有
update
等方法,用于接收被观察者推送过来的更新。该方法接收的参数一般为具体的包括一部分被观察者的对象,或者是整个被观察者对象。ConcreteObserver: 观察者接口的实现类,该类一般有一到n多个,主要对接收到更新后的行为处理。
Observable: 被观察者抽象类,主要有
attach
,detach
和notifyUpdate
等方法,其中attach
主要用于注册观察者;detach
用于解除注册观察者;notifyUpdate
用于下发状态变化后的更新。 相应的方法一般都由自己实现,只是notifyUpdate
中会接收子类传递进来的参数用于推送给观察者。ConcreteObservable: 具体被观察者,该对象里一般并不实现父类中的方法,而是会提供触发更新的方法,也就是接收观察者感兴趣的相应的参数信息,然后将其通知给观察者。
观察者模式示例
1. 抽象被观察者(Observable)
public abstract class Observable {
List<Observer> observers = new ArrayList<>();
protected void attach(Observer o) {
observers.add(o);
}
protected void detach(Observer o) {
observers.remove(o);
}
/**
* 接收观察者感兴趣的数据并推送更新
*
* @param content
*/
protected void notifyUpdate(String content) {
for (int i = 0; i < observers.size(); i++) {
observers.get(i).update(content);
}
}
}
这里主要是创建了一个用于缓存观察者的集合,然后提供了attach
方法来注册观察者,detach
方法来解除注册观察者,notifyUpdate
方法用于推送更新给观察者。
2. 具体观察者(ConcreteObservable)
public class ConcreteObservable extends Observable {
private String content; //用于存储观察者感兴趣的信息
public void setContent(String content) {
this.content = content;
notifyUpdate(content);
}
public String getContent() {
return content;
}
}
该具体观察者主要用于缓存观察者感兴趣的信息,并提供setContent
方法用于触发更新。
3. 观察者接口(Observer)
public interface Observer {
void update(String content);
}
观察者接口只提供一个方法,用于接收推送信息。
4. 具体观察者(ConcreteObserver)
public class ConcreteObserver implements Observer {
@Override
public void update(String content) {
System.out.println("接收到的信息:" + content);
}
}
具体观察者主要是接收被观察者推送过来的信息进行处理。
观察者推模型和拉模型
观察者模式中分为推模型和拉模型,上面我们讲的都是推模型的示例,主要是被观察者行为状态改变的时候,推送给观察者,观察者直接拿到相应的信息进行处理。而拉模型前半部分和推模型一样,也是通过推送的方式把相应的信息传递给观察者。和推模型不同的是:
- 推模型传递的是被观察者的一小部分的公开信息,而拉模型是将被观察者本身作为参数传递给观察者,也就是将被观察者所有的公开信息都暴露给了观察者。
- 当观察者拿到被观察者实例时,自己根据所需获取自己感兴趣的那部分数据,按需获取。
观察者拉模型的示例
/**
* 被观察者
*/
public class Obserable {
private List<Observer> observers = new ArrayList<>();
protected void attach(Observer observer) {
observers.add(observer);
}
protected void dettach(Observer observer) {
observers.remove(observer);
}
protected void notifyObservers() {
for (int i = 0; i < observers.size(); i++) {
observers.get(i).update(this);
}
}
}
/**
* 具体被观察者
*/
public class ConcreteObservable extends Obserable {
private String content;
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
notifyObservers();
}
}
/**
* 观察者接口
* Created by allen on 17/3/19.
*/
public interface Observer {
void update(Obserable observable);
}
/**
* 具体观察者
* Created by allen on 17/3/19.
*/
public class ConcreteObserver implements Observer {
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public void update(Obserable observable) {
ConcreteObservable concreteObservable = (ConcreteObservable) observable;
System.out.println(name + concreteObservable.getContent());
}
}
拉模型的变化主要是推送更新方法里的实现,传递的不在是具体的信息,而是将自己作为参数传递给了观察者,而具体的观察者接收到被观察者实例后,将其强转后,对感兴趣的信息进行了获取。
java自带观察者的实现
public class ConcreteObserver implements Observer {
/**
* @param observable 拉模型中的被观察者实例本身
* @param data 推模型中的感兴趣的信息
*/
@Override
public void update(Observable observable, Object data) {
}
}
public class ConcreteObservable extends Observable {
private String content;
public String getContent() {
return content;
}
public void setChange(String content) {
setChanged();
//拉模型的推送方式
notifyObservers();
//推模型的推送方式
notifyObservers(content);
}
}
java自带的观察者通过实现系统自带的被观察者抽象类和观察者接口,同时实现了推模型和拉模型,我们可以选择其中一种方式来根据需求来进行观察者模式的实现。
总结
1. 观察者模式的优点
- 很好的解决了对象间一对多的通信关系。同时通过接口进行通信,低耦合。
- 支持广播的传播方式。
- 提供推和拉两种模式,让用户按需选择。
2. 观察者模式的缺点
- 在拉模型中,用户需要知道具体的被观察者的实现。导致了不必要的耦合度。
- 由于观察者模式每次都是广播通信,不管需不需要每个观察者都会被调用update方法。如果观察者不需要执行相应的方法,调用了方法导致误更新那就麻烦了。
3. 适用场景
- 当一个对象或者多个对象会随着另一个对象的行为状态发生改变而变化时。
- 当一个对象需要通知其它对象,而又希望和被通知对象的关系是松散耦合的