动机
-
一个对象的状态发生改变,所有的依赖对象(观察者对象)都将得到通知,如果依赖关系过于紧密,将使软件不能很好的抵御变化。
-
使用面向对象技术,可以将这种依赖关系弱化,并形成一种稳定的依赖关系。从而实现软件体系结构的松耦合。
个人理解: 1、什么是 依赖:a 依赖 b a 编译的时候 b 必须存在 2、将关系紧密的依赖通过别的形式转变 比如 进度条控件(ProgressBar * m_progressBar),特定某一窗口空间下的紧耦合,分开来实现(自己形成一个类,eg创建关闭等功能)的松耦合,另外的人都可以调用。
-
一般不使用c++的多继承, 如果有要用多继承,建议另一个类为只是提供接口的父类。
优缺点
-
主要优点如下:
-
降低了目标与观察者之间的耦合关系,两者之间是抽象耦合关系。符合依赖倒置原则。
-
目标与观察者之间建立了一套触发机制。
-
主要缺点如下:
-
目标与观察者之间的依赖关系并没有完全解除,而且有可能出现循环引用。
-
当观察者对象很多时,通知的发布会花费很多时间,影响程序的效率。
定义
- 定义对象间的一种一对多(变化)的依赖关系,以便当一个对象(Subject)的状态发生改变时,所有依赖于它的对象都得到通知并自动更新。
代码(copy他人)
- 瞎编业务:用户购买商品后,使用观察者模式给相应用户添加积分。用户会员到期,使用观察者模式给相应用户发送短信。
注:这里的业务是瞎编乱造的,结尾会给大家提供几个观察者模式在真正企业里面使用的场景
/**
* 观察者的顶层接口
* @param <T>
* <c++ 中为纯虚函数,>
*/
public interface ObserverInterface<T> {
//注册监听者
public void registerListener(T t);
//移除监听者
public void removeListener(T t);
//通知监听者
public void notifyListener(DataEvent t);
}
-
定义抽象的监听者接口 这个接口相当于-群成员(监听者)
-
Listener的顶级接口,为了抽象Listener而存在
public interface MyListener {
void onEvent(DataEvent event);
}
- 定义抽象的事件接口 这个接口相当于群里面发布的通知
@Data
public abstract class DataEvent {
private String msg;
}
- 创建管理者的实现类,相当于具体的群(如微信群,钉钉群)
- 循环调用方式的观察者(同步)(重写抽象接口)
@Component
public class LoopObserverImpl implements ObserverInterface<MyListener> {
//监听者的注册列表
private List<MyListener> listenerList = new ArrayList<>();
@Override
public void registerListener(MyListener listener) {
listenerList.add(listener);
}
@Override
public void removeListener(MyListener listener) {
listenerList.remove(listener);
}
@Override
public void notifyListener(DataEvent event) {
for (MyListener myListener : listenerList) {
myListener.onEvent(event);
}
}
}
创建两个event的实现类,一个是积分事件,一个是短信事件
- 积分事件类
public class ScoreDataEvent extends DataEvent {
private Integer score;
}
- 短信事件类
public class SmsDataEvent extends DataEvent {
private String phoneNum;
}
- 创建两个listener的实现类,一个是处理积分的,一个是处理短信的
/**
* MyListener的实现类,分数监听者
*/
@Component
public class MyScoreListener implements MyListener {
@Override
public void onEvent(DataEvent dataEvent) {
if (dataEvent instanceof ScoreDataEvent) {
//...省略业务逻辑
System.out.println("积分处理:" + dataEvent.getMsg());
}
}
}
/**
- MyListener的实现类,短信监听者
*/
@Component
public class MySmsListener implements MyListener {
@Override
public void onEvent(DataEvent dataEvent) {
if (dataEvent instanceof SmsDataEvent) {
//...省略短信处理逻辑
System.out.println("短信处理");
}
}
}
- 观察者模式的要素就到齐了,我们在main方法里面跑一下
public class Operator {
public static void main(String[] args) {
//通过spring的AnnotationConfigApplicationContext将com.example.demo.user.admin.design路径下的所有加了spring注解的类都扫描放入spring容器
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext("com.example.demo.user.admin.design");
//从spring容器中获取对应bean的实例
LoopObserverImpl loopObserver = context.getBean(LoopObserverImpl.class);
MyScoreListener scoreL = context.getBean(MyScoreListener.class);
MySmsListener smsL = context.getBean(MySmsListener.class);
//向观察者中注册listener
loopObserver.registerListener(scoreL);
loopObserver.registerListener(smsL);
ScoreDataEvent scoreData = new ScoreDataEvent();
scoreData.setMsg("循环同步观察者");
//发布积分事件,通知监听者
loopObserver.notifyListener(scoreData);
/*******************************************/
//从spring容器获取QueueObserverImpl观察者
QueueObserverImpl queueObserver = context.getBean(QueueObserverImpl.class);
//向观察者中注册listener
queueObserver.registerListener(scoreL);
queueObserver.registerListener(smsL);
ScoreDataEvent scoreData1 = new ScoreDataEvent();
scoreData1.setMsg("队列异步观察者");
//发布积分事件,通知监听者
queueObserver.notifyListener(scoreData1);
}
}
- 接下来看看下面这个新的观察者实现类和上面示例中的的观察者实现类LoopObserverImpl有什么不同吗
/**
* 启动一个线程循环阻塞队列的观察者,可以实现解耦异步。
*/
@Component
public class QueueObserverImpl implements ObserverInterface<MyListener> {
//监听者的注册列表
private List<MyListener> listenerList = new ArrayList<>();
//创建一个大小为10的阻塞队列
private BlockingQueue<DataEvent> queue = new LinkedBlockingQueue<>(10);
//创建一个线程池
private ExecutorService executorService = new ScheduledThreadPoolExecutor(1, r -> {
Thread t = new Thread(r);
t.setName("com.kangarooking.observer.worker");
t.setDaemon(false);
return t;
});
// private ExecutorService executorService = Executors.newFixedThreadPool(1);
@Override
public void registerListener(MyListener listener) {
listenerList.add(listener);
}
@Override
public void removeListener(MyListener listener) {
listenerList.remove(listener);
}
@Override
public void notifyListener(DataEvent event) {
System.out.println("向队列放入DataMsg:" + event.getMsg());
queue.offer(event);
}
@PostConstruct
public void initObserver() {
System.out.println("初始化时启动一个线程");
executorService.submit(() -> {
while (true) {
try {
System.out.println("循环从阻塞队列里面获取数据,take是阻塞队列没有数据就会阻塞住");
DataEvent dataMsg = queue.take();
System.out.println("从阻塞队列获取到数据:" + dataMsg.getMsg());
eventNotify(dataMsg);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}
private void eventNotify(DataEvent event) {
System.out.println("循环所有的监听者");
for (MyListener myListener : listenerList) {
myListener.onEvent(event);
}
}
}
不同之处就是引入了阻塞队列,让通知这个操作变成异步操作,既只需要将event时间放入阻塞队列之后就可以直接返回了。不用像LoopObserverImpl要等到listener注册表循环完毕才能返回。这样就实现了通知操作和循环listener注册表的解耦和异步。
举例说明异步实现和同步实现的区别: 同步:还是团建群的例子,假如领导是保姆型领导,通知下来任务之后可能不太放心,要挨个问,小张你准备什么表演阿,大概多久能准备好鸭。小红你呢→_→。。。 异步:假如是甩手掌柜型领导,发布完消息之后他就不管了。 上面就是同步和异步的区别,同步就是领导是个保姆,挨个问挨个了解情况之后这个事情才算完。异步就是领导发布完消息就完事儿。
(copy)
结构图
- 上为稳定,下为变化
总结
- 使用面向对象的抽象,Observer模式使得我们可以独立地改变目 标与观察者,从而使二者之间的依赖关系达致松耦合。
- 目标发送通知时,无需指定观察者,通知(可以携带通知信息作 为参数)会自动传播。
- 观察者自己决定是否需要订阅通知,目标对象对此一无所知。
- Observer模式是基于事件的UI框架中非常常用的设计模式,也是MVC模式的一个重要组成部分。