设计模式二:观察者模式(发布订阅模式)

我的简书:https://www.jianshu.com/u/c91e642c4d90
我的CSDN:http://blog.csdn.net/wo_ha
我的GitHub:https://github.com/chuanqiLjp
我的个人博客:https://chuanqiljp.github.io/

版权声明:商业转载请联系我获得授权,非商业转载请在醒目位置注明出处。

###版权声明,转载请著名出处:http://www.jianshu.com/p/f88dca81c56b

#设计模式系列

1. 设计模式一:单例模式

2. 设计模式二:观察者模式(发布订阅模式)

#定义
观察者模式定义了一个一对多的依赖关系,能让一个或者多个观察者对象监督一个主题对象,这样一个主题对象在状态上的变化能够通知所有的依赖于此对象的那些观察者对象,使这些观察者对象能够自动更新,其本质是触发联动,别名:发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。

#观察者模式的四个角色
1.抽象主题(Subject)角色:抽象主题角色把所有对观察者对象的引用保存在一个聚集(比如ArrayList对象)里,每个主题都可以有任何数量的观察者。抽象主题提供一个接口,可以增加和删除观察者对象,抽象主题角色又叫做抽象被观察者(Observable)角色。一般是一个抽象类或者一个接口来实现,常见的方法attatch(),detach(),notifyObserver();java提供java.util.Observable类支持,
2.具体主题(ConcreteSubject)角色:将有关状态存入具体观察者对象;在具体主题的内部状态改变时,给所有登记过的观察者发出通知。具体主题角色又叫做具体被观察者(Concrete Observable)角色。
3.抽象观察者(Observer)角色: 为所有的具体观察者定义一个接口,在得到主题的通知时更新自己,这个接口叫做更新接口。定义的方法有update(),Java提供了java.util.Observer接口支持,
4.具体观察者(ConcreteObserver)角色: 存储与主题的状态自恰的状态。具体观察者角色实现抽象观察者角色所要求的更新接口,以便使本身的状态与主题的状态 像协调。如果需要,具体观察者角色可以保持一个指向具体主题对象的引用。

#观察者模式的分类
观察者模式分为推模型和拉模型,其中区别为:
推模型:假定目标对象知道观察者需要的数据,会推送指定的数据,会使观察者对象难以复用;
拉模型:目标对象不知道观察者具体需要什么数据,因此把自身的引用传给观察者,由观察者来取值;

#观察者模式的使用步骤
1.创建目标对象
2.创建观察者对象
3.观察者对象订阅目标对象
4.目标对象更新数据通知订阅者对象

#何时使用观察者模式
1.当一个抽象模型,其中一个方面的操作依赖于另一个方面的状态变化
2.如果在更改一个对象的时候,需要同时连带改变其他的对象,而且不知道究竟应该有多少对象需要被连带改变时
3.当一个对象必须通知其他的对象,但是你又希望这个对象和其他被他通知的对象是松散耦合的

#观察者模式的代码示例

###一:观察者模式的标准代码模版
抽象主题(Subject)角色

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public abstract class Subject {
private List<Observer> observers=new ArrayList<Observer>();//用于保存已订阅的目标的观察者
public void attach(Observer observer){//订阅目标对象
observers.add(observer);
}
public void detach(Observer observer){//解除订阅关系
observers.remove(observer);
}
protected void notifyObserver(){//通知已订阅的观察者
for (Observer observer : observers) {
observer.updata(this);
}
}
}

具体主题(ConcreteSubject)角色

1
2
3
4
5
6
7
8
9
10
public class ConcreteSubject extends Subject {
private String subjectState;
public String getSubjectState() {
return subjectState;
}
public void updateChange(String subjectState) {//目标状态改变,通知订阅者
this.subjectState = subjectState;
notifyObserver();
}
}

抽象观察者(Observer)角色

1
2
3
public interface Observer {
public void updata(Subject subject); //目标对象发生改变时调用
}

具体观察者(ConcreteObserver)角色

1
2
3
4
5
6
public class ConcreteObserver implements Observer{
private String subjectState;
public void updata(Subject subject) {
subjectState=((ConcreteSubject)subject).getSubjectState();//记录目标对象改变的状态值
}
}

###二:观察者模式的标准代码模版的情景使用
背景:老王在气象局上班,老王需要在天气良好的时候发一条消息给女朋友提醒约会,发一条消息给老妈提醒扫货(注:未书写的类均为模版代码不变的)
具体的天气被观察者,这里指代老王或气象局

1
2
3
4
5
6
7
8
9
10
public class ConcreteSubject extends Subject {
private String subjectWeatherState;//天气状态
public String getSubjectWeatherState() {
return subjectWeatherState;
}
public void updateChangeWeatherState(String subjectWeatherState) {
this.subjectWeatherState = subjectWeatherState;
notifyObserver();
}
}

具体的观察者,这里指代女朋友和老妈

1
2
3
4
5
6
7
8
9
10
11
12
13
public class ConcreteObserver implements Observer{
private String subjectWeatherState;//天气状态
private String subjectName;//订阅者的名字,get、set方法省略
private String redim;//需要提示的消息 ,get、set方法省略
public ConcreteObserver(String subjectName, String redim) {
this.subjectName = subjectName;
this.redim = redim;
}
public void updata(Subject subject) {
subjectWeatherState=((ConcreteSubject)subject).getSubjectWeatherState();
System.out.println(subjectName+"收到了消息,内容:"+subjectWeatherState+","+redim);
}
}

测试使用类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ClientTest {
public static void main(String[] args) {
// 1.创建目标对象
ConcreteSubject subject=new ConcreteSubject();
// 2.创建观察者对象
ConcreteObserver observerGirl=new ConcreteObserver("女朋友", "第一次约会,地点欢乐谷,不见不散");
ConcreteObserver observerMum=new ConcreteObserver("老妈", "沃尔玛超市打折,扫货");
// 3.观察者对象订阅目标对象
subject.attach(observerGirl);
subject.attach(observerMum);
// 4.目标对象更新数据通知订阅者对象
subject.updateChangeWeatherState("天气晴朗,蓝天白云,气温23度");
}
}

测试的结果:

1
2
女朋友收到了消息,内容:天气晴朗,蓝天白云,气温23度,第一次约会,地点欢乐谷,不见不散
老妈收到了消息,内容:天气晴朗,蓝天白云,气温23度,沃尔玛超市打折,扫货

###三:Java提供支持的观察者的情景使用
背景:一个商品价格变化需要通知已经订阅的VIP顾客
具体的目标对象(被观察者),指代商品

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.util.Observable;
public class ProductSubject extends Observable{
private String name;//商品名字
private float price;//商品价格
public ProductSubject(String name){
this.name=name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public float getPrice() {
return price;
}
public void setPrice(float price) {
this.price = price;
setChanged();//设置变化点,必须在通知之前调用
notifyObservers();
}
}

具体的观察者,指代VIP顾客

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import java.util.Observable;
import java.util.Observer;
public class VipObserver implements Observer{
private String customerName;//顾客姓名
public VipObserver(String customerName){
this.customerName=customerName;
}
/**
* @param observable
* 拉模式的情况,目标对象
* @param obj
* 推模式的情况,指定的内容
*/
public void update(Observable observable, Object obj) {
String name = ((ProductSubject)observable).getName();
float price = ((ProductSubject)observable).getPrice();
System.out.println(customerName+"收到商品:"+name+" 的价格变化通知,现在的价格为:"+price+" 元");
}
}

测试使用类,用于测试商品价格发生变化会通知Vip客户

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class ClientTest {
public static void main(String[] args) {
//1.创建被观察对象即目标对象
ProductSubject subject=new ProductSubject("苹果");
//2.创建观察者
VipObserver observerM=new VipObserver("老马哥");
VipObserver observerW=new VipObserver("隔壁老王");
//3.观察者订阅目标对象
subject.addObserver(observerM);
subject.addObserver(observerW);
//4.目标对象发生改变
subject.setPrice(10.5f);
}
}

测试的结果:

1
2
隔壁老王收到商品:苹果 的价格变化通知,现在的价格为:10.5元
老马哥收到商品:苹果 的价格变化通知,现在的价格为:10.5元

#观察者模式的总结

###观察者的优点
1.实现了观察者和目标之间的抽象耦合
2.实现了动态的联动
3.支持广播通信

###观察者的缺点
1.可能会引起无谓的操作,有些订阅者不需要响应(区别对待观察者)
2.在Java中消息的通知一般是顺序执行,那么一个观察者卡顿,会影响整体的执行效率,在这种情况下,一般会采用异步实现。

###Android中常见的观察者模式
OnClickListener、ContentObserver、android.database.Observable等;还有组件通讯库RxJava、RxAndroid、EventBus;Adapter的notifyDataSetChanged()

我的CSDN博客地址:http://blog.csdn.net/wo_ha/article/details/77717887