观察者模式 Observer
根据案例分析传统模式中可能存在的问题
案例
客户签署放款合同,当合同签署完毕后,通知财务进行放款
- 创建客户类,提供合同签署方法
//客户
class User{
public String name; //客户名称
public String contractType; //合同类型
//持有财务对象,当签署合同成功时,通知财务方框
public Accountant accountant;
//构造器
public User(String name, String contractType, Accountant accountant) {
this.name = name;
this.contractType = contractType;
this.accountant = accountant;
}
//合同签署方法
public void sign(){
System.out.println("用户"+name+"签署"+contractType);
System.out.println("开始通知财务放款");
//财务调用grant()方法放款
accountant.grant(name);
}
}
- 创建财务类,提供放款方法,当客户签署合同完毕后,通过财务对象调用该方法实现放款
//会计
class Accountant{
public String accountantName; //财务姓名
//构造器
public Accountant(String accountantName) {
this.accountantName = accountantName;
}
//放款方法
public void grant(String userName){
System.out.println("财务"+accountantName+"给客户"+userName+"放款");
}
}
- 调用测试
public static void main(String[] args) {
//创建财务对象
Accountant accountant = new Accountant("旺财");
//创建用户
User user = new User("小明","放款合同", accountant);
//签署合同
user.sign();
}
- 分析可能存在的问题: 假设当用户签署合同后,除了要通知财务放款以外还需要通知其他人怎么办? 例如通知公司hr,对该客户的负责人增加绩效,通知该客户的负责人,你的客户已签署合同…在现在的传统模式下如果要增加其他通知对象,除了要创建其他通知对象,增加通知方法以外,最主要的是要修改现在的User类,将要通知的其他对象添加到User中,签署合同成功以后,通过User中持有的通知对象,调用方法,执行后续操作,User中持有需要通知的对象,代码耦合,在修改User添加新通知对象时,违反了OCP开闭原则,并且无法做到动态添加,删除通知对象
观察者模式讲解
简单理解观察者模式
观察者模式又被称为发布订阅模式,例如公众号,订阅公众号的用户就是观察者,可以关注,与取消公众号,公众号本身是被观察者,当公众号发布消息时,可以将消息推送给它的所有关注者
观察者模式角色分析
- 抽象被观察者 Subject : 例如公众号,可能有多个不同的公众号,所以进行抽象,定义了可以添加,删除,通知观察者的抽象方法
- 具体被观察者 ConcreteSubject : 例如某个公众号,就是具体被观察者,被观察者持有保存观察者对象的集合,例如一个公众号可以被多个用户订阅,并实现添加用户,删除用户,发布消息时通知用户的方法,也就是用户关注时将用户信息存入集合中,用户取消关注时,将集合中的该用户删除,发布消息时遍历集合中的所有用户,将消费推送给所有用户
- 抽象观察者 Observer : 例如关注公众号的用户,有多个,也存在不同用户,例如企业微信用户,普通微信用户,商业微信用户等,都可以关注公众号,所以创建抽象,定义了更新自身状态的抽象方法
- 具体观察者 ConcrereObserver: 实现抽象观察者,重写更新方法,例如当有公众号推送文章,调用更新方法,将自己的消息系统添加一条未读信息
- 重点关注,添加,删除,通知观察者,与观察者更新状态的方法
代码示例
- 案例: 明星发布动态,粉丝关注明星,查看明星动态
- 分析: 明星为被观察者,关注的粉丝为观察者,可以有多个粉丝,粉丝可以关注,可以取消关注
- 创建抽象被观察者
interface Subject{
//注册(关注)
public void registerObserver(Observer o);
//移出(取消关注)
public void removeObserver(Observer o);
//通知(发布动态时推送消息)
public void notifyObservers();
}
- 创建具体被观察者,明星
//具体被观察者
class StarSubject implements Subject{
//明星发布的动态
private String JsonObj;
//持有多个Observer观察者(已经关注的用户)
private List<Observer> observerList = new ArrayList<>();
//通过构造器进行初始化
public StarSubject(List<Observer> observerList) {
this.observerList = observerList;
}
//注册,向observerList容器中添加新的观察者
@Override
public void registerObserver(Observer o) {
observerList.add(o);
}
//删除,在observerList中删除某个观察者
@Override
public void removeObserver(Observer o) {
observerList.remove(o);
}
//通知,假设当前明星更新了动态,将给信的动态推送给粉丝
//通知我方页面进行刷新展示
@Override
public void notifyObservers() {
for(Observer o: observerList) {
o.update(JsonObj);
}
}
//明星发布动态方法
public void dataChange(String JsonObj) {
this.JsonObj = JsonObj;
//将发布的动态推送给粉丝
notifyObservers();
}
}
- 创建抽象观察者,提供更新自身的抽象方法
//抽象观察者,提供了更新自身的抽象方法
interface Observer{
public void update(String objJson);
}
- 创建具体观察者,粉丝
//具体观察者,实现抽象观察者,重写更新方法
class FansObserver implements Observer{
//信息
private String weatherDesc;
//显示关注的明星的动态
public void display() {
System.out.println("查看我关注的明星动态:" + weatherDesc);
}
//当明星发布动态时,通过该方法,将动态信息传递给当前粉丝
@Override
public void update(String JsonObj) {
this.weatherDesc = JsonObj;
}
}
- 调用测试
public static void main(String[]args) {
List<Observer> obsList = new ArrayList<>();
//创建粉丝对象
FansObserver fansObserver = new FansObserver();
//粉丝关注明星,将粉丝添加到明星被观察者集合持有的集合中
obsList.add(fansObserver);
//创建明星对象被观察者对象,并对持有的观察者赋值
StarSubject star = new StarSubject(obsList);
//明星发布动态
star.dataChange("我是明星超级猛男,我发布新的动态了");
//粉丝查看明星的动态
fansObserver.display();
}
流程图
Spring 中观察者模式使用案例
查看 Spring 中事件监听的功能,就是通过观察者模式来搭建的,
- Spring为容器内事件定义了一个抽象类ApplicationEvent,该类继承了JDK中的事件基类EventObject
- Spring定义了一个ApplicationListener接口作为为事件监听器的抽象该接口该接口继承了JDK中表示事件监听器的标记接口EventListener,内部只定义了一个抽象方法onApplicationEvent(evnt),当监听的事件在容器中被发布,该方法将被调用。同时,该接口是一个泛型接口,其实现类可以通过传入泛型参数指定该事件监听器要对哪些事件进行监听。这样有什么好处?这样所有的事件监听器就可以由一个事件发布器进行管理,并对所有事件进行统一发布,而具体的事件和事件监听器之间的映射关系,则可以通过反射读取泛型参数类型的方式进行匹配,最后,所有的事件监听器都必须向容器注册,容器能够对其进行识别并委托容器内真正的事件发布器进行管理
- ApplicationContext接口继承了ApplicationEventPublisher接口,从而提供了对外发布事件的能力,ApplicationContext,即容器本身仅仅是对外提供了事件发布的接口,真正的工作其实是委托给了容器内部一个ApplicationEventMulticaster对象,真正的事件发布器是ApplicationEventMulticaster,这是一个接口,定义了事件发布器需要具备的基本功能:管理事件监听器以及发布事件。其默认实现类是SimpleApplicationEventMulticaster,该组件会在容器启动时被自动创建,并以单例的形式存在,管理了所有的事件监听器,并提供针对所有容器内事件的发布功能。
- 我们可以把被观察者(Subject)替换成事件(event),把对特定主题进行观察的观察者(Observer)替换成对特定事件进行监听的监听器(EventListener),而把原有主题中负责维护主题与观察者映射关系以及在自身状态改变时通知观察者的职责从中抽出,放入一个新的角色事件发布器(EventPublisher)中
/**
* 继承了ApplicationContextEvent,就是个容器事件
*/
public class MailSendEvent extends ApplicationContextEvent {
private String to; //目的地
public MailSendEvent(ApplicationContext source, String to) {
super(source);
this.to = to;
}
public String getTo(){
return this.to;
}
}
@Component
public class MailSendListener implements ApplicationListener<MailSendEvent>{
@Override
public void onApplicationEvent(MailSendEvent mailSendEvent) {
MailSendEvent event = mailSendEvent;
System.out.println("MailSender向"+ event.getTo()+ "发送了邮件");
}
}
发布事件
@Component("mailSender")
public class MailSender {
@Autowired
private ApplicationContext applicationContext; //容器事件由容器触发
public void sendMail(String to){
System.out.println("MailSender开始发送邮件");
MailSendEvent event = new MailSendEvent(applicationContext,to);
applicationContext.publishEvent(event);
}
}
调用测试
public class SpringEventTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("web/WEB-INF/applicationContext.xml");
MailSender sender = (MailSender)context.getBean("mailSender");
sender.sendMail("北京");
}
}