目录
观察者模式(observer pattern)的原始定义是:定义对象之间的一对多依赖关系,这样当一个对象改变状态时,它的所有依赖项都会自动得到通知和更新。
解释一下上面的定义: 观察者模式它是用于建立一种对象与对象之间的依赖关系,一个对象发生改变时将自动通知其他对象,其他对象将相应的作出反应。
在观察者模式中发生改变的对象称为观察目标,而被通知的对象称为观察者,一个观察目标可以应对多个观察者,而且这些观察者之间可以没有任何相互联系,可以根据需要增加和删除观察者,使得系统更易于扩展。
观察者模式的别名有发布-订阅(Publish/Subscribe)模式,模型-视图(ModelView)模式、源-监听(Source-Listener) 模式等。
观察者模式的应用场景非常广泛,小到代码层面的解耦,大到架构层面的系统解耦,再或者 一些产品的设计思路,都有这种模式的影子。
现在我们常说的基于事件驱动的架构,其实也是观察者模式的一种最佳实践。当我们观察某一个对象时,对象传递出的每一个行为都被看成是一个事件,观察者通过处理每一个事件来完成自身的操作处理。
生活中也有许多观察者模式的应用,比如 汽车与红绿灯的关系,'红灯停,绿灯行',在这个过程中交通信号灯是汽车的观察目标,而汽车是观察者。
在观察者模式中有如下角色:
抽象主题角色把所有观察者对象保存在一个集合里,每个主题都可以有任意数量的观察者,抽象主题提供一个
接口,可以增加和删除观察者对象。
该角色将有关状态存入具体观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。
是观察者的抽象类,它定义了一个更新接口,使得在得到主题更改通知时更新自己。
实现抽象观察者定义的更新接口,以便在得到主题更改通知时更新自身的状态。在具体观察者中维护一个指向具体目标对象的引用,它存储具体观察者的有关状态,这些状态需要与具体目标保持一致。
package main.java.cn.test.observer.V1;
/**
* @author ningzhaosheng
* @date 2024/1/14 18:02:40
* @description 抽象观察者
*/
public interface Observer {
//update方法: 为不同观察者的更新(响应)行为定义相同的接口,不同的观察者对该方法有不同的实现
public void update();
}
package main.java.cn.test.observer.V1;
/**
* @author ningzhaosheng
* @date 2024/1/14 18:03:10
* @description 具体观察者
*/
public class ConcreteObserverOne implements Observer {
@Override
public void update() {
//获取消息通知,执行业务代码
System.out.println("ConcreteObserverOne 得到通知!");
}
}
package main.java.cn.test.observer.V1;
/**
* @author ningzhaosheng
* @date 2024/1/14 18:03:49
* @description 具体观察者
*/
public class ConcreteObserverTwo implements Observer {
@Override
public void update() {
//获取消息通知,执行业务代码
System.out.println("ConcreteObserverTwo 得到通知!");
}
}
package main.java.cn.test.observer.V1;
/**
* @author ningzhaosheng
* @date 2024/1/14 18:04:25
* @description 抽象目标类
*/
public interface Subject {
void attach(Observer observer);
void detach(Observer observer);
void notifyObservers();
}
package main.java.cn.test.observer.V1;
import java.util.ArrayList;
/**
* @author ningzhaosheng
* @date 2024/1/14 18:05:03
* @description 具体目标类
*/
public class ConcreteSubject implements Subject {
//定义集合,存储所有观察者对象
private ArrayList<Observer> observers = new ArrayList<>();
//注册方法,向观察者集合中增加一个观察者
@Override
public void attach(Observer observer) {
observers.add(observer);
}
//注销方法,用于从观察者集合中删除一个观察者
@Override
public void detach(Observer observer) {
observers.remove(observer);
}
//通知方法
@Override
public void notifyObservers() {
//遍历观察者集合,调用每一个观察者的响应方法
for (Observer obs : observers) {
obs.update();
}
}
}
package main.java.cn.test.observer.V1;
/**
* @author ningzhaosheng
* @date 2024/1/14 18:06:52
* @description 测试类
*/
public class Test {
public static void main(String[] args) {
//创建目标类(被观察者)
ConcreteSubject subject = new ConcreteSubject();
//注册观察者类,可以注册多个
subject.attach(new ConcreteObserverOne());
subject.attach(new ConcreteObserverTwo());
//具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。
subject.notifyObservers();
}
}
接下来我们使用观察模式,来实现一个买房摇号的程序。摇号结束,需要通过短信告知用户摇号结果,还需要想MQ中保存用户本次摇号的信息。
package main.java.cn.test.observer.V2;
/**
* @author ningzhaosheng
* @date 2024/1/14 18:23:16
* @description 开奖服务接口
*/
public interface LotteryService {
//摇号相关业务
public LotteryResult lottery(String uId);
}
package main.java.cn.test.observer.V2;
import java.util.Date;
/**
* @author ningzhaosheng
* @date 2024/1/14 18:23:53
* @description 开奖服务
*/
public class LotteryServiceImpl implements LotteryService {
//注入摇号服务
private DrawHouseService houseService = new DrawHouseService();
@Override
public LotteryResult lottery(String uId) {
//摇号
String result = houseService.lots(uId);
//发短信
System.out.println("发送短信通知用户ID为: " + uId + ",您的摇号结果如下: " + result);
//发送MQ消息
System.out.println("记录用户摇号结果(MQ), 用户ID:" + uId + ",摇号结果:" + result);
return new LotteryResult(uId, result, new Date());
}
}
package main.java.cn.test.observer.V2;
import java.util.Date;
/**
* @author ningzhaosheng
* @date 2024/1/14 18:22:13
* @description 摇号结果
*/
public class LotteryResult {
private String uId; // 用户id
private String msg; // 摇号信息
private Date dataTime; // 业务时间
public LotteryResult(String uId, String msg, Date dataTime) {
this.uId = uId;
this.msg = msg;
this.dataTime = dataTime;
}
public String getuId() {
return uId;
}
public void setuId(String uId) {
this.uId = uId;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Date getDataTime() {
return dataTime;
}
public void setDataTime(Date dataTime) {
this.dataTime = dataTime;
}
@Override
public String toString() {
return "LotteryResult{" +
"uId='" + uId + '\'' +
", msg='" + msg + '\'' +
", dataTime=" + dataTime +
'}';
}
}
package main.java.cn.test.observer.V2;
/**
* @author ningzhaosheng
* @date 2024/1/14 18:21:39
* @description 模拟买房摇号服务
*/
public class DrawHouseService {
//摇号抽签
public String lots(String uId) {
if (uId.hashCode() % 2 == 0) {
return "恭喜ID为: " + uId + " 的用户,在本次摇号中中签! !";
} else {
return "很遗憾,ID为: " + uId + "的用户,您本次未中签!!";
}
}
}
package main.java.cn.test.observer.V2;
/**
* @author ningzhaosheng
* @date 2024/1/14 18:26:36
* @description 测试类
*/
public class Test {
public static void main(String[] args) {
LotteryService ls = new LotteryServiceImpl();
LotteryResult result = ls.lottery("1234567887654322");
System.out.println(result.toString());
}
}
上面的摇号业务中,摇号、发短信、发MQ消息是一个顺序调用的过程,但是除了摇号这个核心功能以外,发短信与记录信息到MQ的操作都不是主链路的功能,需要单独抽取出来,这样才能保证在后面的开发过程中保证代码的可扩展性和可维护性。
package main.java.cn.test.observer.V3;
import main.java.cn.test.observer.V2.LotteryResult;
/**
* @author ningzhaosheng
* @date 2024/1/15 10:59:49
* @description 事件监听接口
*/
public interface EventListener {
void doEvent(LotteryResult result);
}
package main.java.cn.test.observer.V3;
import main.java.cn.test.observer.V2.LotteryResult;
/**
* @author ningzhaosheng
* @date 2024/1/15 11:00:26
* @description 短信发送事件
*/
public class MessageEventListener implements EventListener {
@Override
public void doEvent(LotteryResult result) {
System.out.println("发送短信通知用户ID为: " + result.getuId() + ",您的摇号结果如下: " + result.getMsg());
}
}
package main.java.cn.test.observer.V3;
import main.java.cn.test.observer.V2.LotteryResult;
/**
* @author ningzhaosheng
* @date 2024/1/15 11:01:06
* @description MQ消息发送事件
*/
public class MQEventListener implements EventListener {
@Override
public void doEvent(LotteryResult result) {
System.out.println("记录用户摇号结果(MQ), 用户ID:" + result.getuId() + ",摇号结果:" + result.getMsg());
}
}
package main.java.cn.test.observer.V3;
import main.java.cn.test.observer.V2.LotteryResult;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* @author ningzhaosheng
* @date 2024/1/15 11:01:46
* @description 事件处理类
*/
public class EventManager {
public enum EventType {
MQ, Message
}
//监听器集合
Map<Enum<EventType>, List<EventListener>> listeners = new
HashMap<>();
public EventManager(Enum<EventType>... operations) {
for (Enum<EventType> operation : operations) {
this.listeners.put(operation, new ArrayList<>());
}
}
/**
* 订阅
*
* @param eventType 事件类型
* @param listener 监听
*/
public void subscribe(Enum<EventType> eventType,
EventListener listener) {
List<EventListener> users = listeners.get(eventType);
users.add(listener);
}
/**
* 取消订阅
*
* @param eventType 事件类型
* @param listener 监听
*/
public void unsubscribe(Enum<EventType>
eventType, EventListener listener) {
List<EventListener> users = listeners.get(eventType);
users.remove(listener);
}
/**
* 通知
*
* @param eventType 事件类型
* @param result 结果
*/
public void notify(Enum<EventType> eventType,
LotteryResult result) {
List<EventListener> users = listeners.get(eventType);
for (EventListener listener : users) {
listener.doEvent(result);
}
}
}
package main.java.cn.test.observer.V3;
import main.java.cn.test.observer.V2.LotteryResult;
/**
* @author ningzhaosheng
* @date 2024/1/15 11:03:21
* @description 开奖服务接口
*/
public abstract class LotteryService {
private EventManager eventManager;
public LotteryService() {
//设置事件类型
eventManager = new EventManager(EventManager.EventType.MQ, EventManager.EventType.Message);
//订阅
eventManager.subscribe(EventManager.EventType.Message, new MessageEventListener());
eventManager.subscribe(EventManager.EventType.MQ, new MQEventListener());
}
public LotteryResult lotteryAndMsg(String uId) {
LotteryResult result = lottery(uId);
//发送通知
eventManager.notify(EventManager.EventType.Message, result);
eventManager.notify(EventManager.EventType.MQ, result);
return result;
}
public abstract LotteryResult lottery(String uId);
}
package main.java.cn.test.observer.V3;
import main.java.cn.test.observer.V2.DrawHouseService;
import main.java.cn.test.observer.V2.LotteryResult;
import java.util.Date;
/**
* @author ningzhaosheng
* @date 2024/1/15 11:05:14
* @description 开奖服务
*/
public class LotteryServiceImpl extends LotteryService {
//注入摇号服务
private DrawHouseService houseService = new DrawHouseService();
@Override
public LotteryResult lottery(String uId) {
//摇号
String result = houseService.lots(uId);
return new LotteryResult(uId, result, new Date());
}
}
package main.java.cn.test.observer.V3;
import main.java.cn.test.observer.V2.LotteryResult;
/**
* @author ningzhaosheng
* @date 2024/1/15 11:07:14
* @description 测试类
*/
public class Test {
public static void main(String[] args) {
LotteryService ls = new LotteryServiceImpl();
LotteryResult result = ls.lotteryAndMsg("1234567887654322");
System.out.println(result);
}
}
比如,商品库存数量发生变化时,需要通知商品详情页、购物车等系统改变数量。
比如,订阅微信公众号的文章,发送者通过公众号发送,订阅者并不知道哪些用户订阅了公众号。
比如,在系统中创建一个触发链,A 对象的行为将影响 B 对象,B 对象的行为将影响 C 对象……这样通过观察者模式能够很好地实现。
这是观察者模式的典型应用场景,一个人发微博或朋友圈,只要是关联的朋友都会收到通知;一旦取消关注,此人以后将不会收到相关通知。
比如,基于 Java UI 的编程,所有键盘和鼠标事件都由它的侦听器对象和指定函数处理。当用户单击鼠标时,订阅鼠标单击事件的函数将被调用,并将所有上下文数据作为方法参数传递给它。
JDK中提供了Observable类以及Observer接口,它们构成了JDK对观察者模式的支持。
该接口中声明了一个方法,它充当抽象观察者,其中声明了一个update方法。
void update(Observable o, Object arg);
充当观察目标类(被观察类) , 在该类中定义了一个Vector集合来存储观察者对象.下面是它最重要的 3 个方法。
void addObserver(Observer o) 方法:用于将新的观察者对象添加到集合中。
void notifyObservers(Object arg) 方法:调用集合中的所有观察者对象的 update方法,通知它们数据发生改变。通常越晚加入集合的观察者越先得到通知。
void setChange() 方法:用来设置一个 boolean 类型的内部标志,注明目标对象发生了变化。当它为true时,notifyObservers() 才会通知观察者。
用户可以直接使用Observer接口和Observable类作为观察者模式的抽象层,再自定义具体观察者类和具体观察目标类,使用JDK中提供的这两个类可以更加方便的实现观察者模式。
好了,本次分享就到这里,欢迎大家继续阅读《设计模式》专栏其他设计模式内容,如果有帮助到大家,欢迎大家点赞+关注+收藏,有疑问也欢迎大家评论留言!