目录
一、基本概念
二、UML类图
三、角色设计
四、代码实现
案例一
案例二
案例三
五、总结
观察者先订阅被观察者对象,当被观察者的状态发生变化时,观察者可以及时收到消息,在这种模式当中,被观察者维护了一个观察者列表,并提供了添加、删除、通知观察者的方法。
角色 | 描述 |
---|---|
抽象主题 | 抽象被观察者,提供注册和移除观察者、通知观察者的接口 |
具体主题 | 将有关状态存入具体观察者对象,当自己的状态发生改变时,给所有登记过的观察者发出通知 |
抽象观察者 | 定义了一个更新接口,使得在得到主题更改通知时更新自己 |
具体观察者 | 实现抽象观察者接口,以便在得到主题的通知时更新自身的状态 |
这边我分享了三个案例,分别是通过自定义撰写、JDK源码封装和Spring框架封装三种去实现。
假设有一个被观察者:CSDN博主,它有2个粉丝分别是Jack和Tom,当CSDN发布了通知,对应的粉丝都会收到私信。
定义被观察者接口:
public interface Blogger {
/**
* 新增粉丝
*/
void addFans(Fans fans);
/**
* 移除粉丝
*/
void removeFans(Fans fans);
/**
* 通知粉丝
*/
void sendMessage(String message);
}
被观察者具体实现类:
import java.util.ArrayList;
import java.util.List;
public class CSDN implements Blogger {
private List fansList = new ArrayList<>();
@Override
public void addFans(Fans fans) {
this.fansList.add(fans);
}
@Override
public void removeFans(Fans fans) {
this.fansList.remove(fans);
}
@Override
public void sendMessage(String message) {
for(Fans fans : fansList){
fans.receiveMessage(message);
}
}
}
定义观察者接口:
public interface Fans {
void receiveMessage(String message);
}
定义具体观察者实现类:
public class JackFans implements Fans {
@Override
public void receiveMessage(String message) {
System.out.println("Jack收到了私信:"+message);
}
}
public class TomFans implements Fans {
@Override
public void receiveMessage(String message) {
System.out.println("Tom收到了私信:"+message);
}
}
客户端:
public class Client{
public static void main(String[] args) {
Blogger blogger = new CSDN();
Fans jackFans = new JackFans();
Fans tomFans = new TomFans();
blogger.addFans(jackFans);
blogger.addFans(tomFans);
blogger.sendMessage("CSDN发布了《关于社区整顿的通知》");
blogger.removeFans(jackFans);
blogger.sendMessage("CSDN发布了《关于博客发布调整的通知》");
}
}
运行结果如下:
这个案例依旧是实现上述逻辑,只不过我们使用JDK提供的接口去实现,过程如下。
定义被观察者具体实现类:
import java.util.Observable;
public class CSDN extends Observable {
@Override
public void notifyObservers(Object arg) {
//修改状态为可以群发
setChanged();
//调用父类的notifyObservers 群发消息
super.notifyObservers(arg);
}
}
定义观察者具体实现类:
import java.util.Observable;
import java.util.Observer;
public class TomFans implements Observer {
@Override
public void update(Observable o, Object arg) {
System.out.println("Tom收到了私信:"+arg);
}
}
import java.util.Observable;
import java.util.Observer;
public class JackFans implements Observer {
@Override
public void update(Observable o, Object arg) {
System.out.println("Jack收到了私信:"+arg);
}
}
客户端:
import java.util.Observable;
import java.util.Observer;
public class Client{
public static void main(String[] args) {
Observable csdn = new CSDN();
Observer tomFans = new TomFans();
Observer jackFans = new JackFans();
csdn.addObserver(tomFans);
csdn.addObserver(jackFans);
csdn.notifyObservers("《关于整顿社区的通知》");
csdn.deleteObserver(tomFans);
csdn.notifyObservers("《关于更新CSDN社区的通知》");
}
}
运行结果如下:
有了JDK提供的接口去实现,整体代码结构更简洁和方便了!
这个案例依旧是上述逻辑,只不过我们这边使用Spring提供的封装事件的监听去实现,这边省去了搭建Spring框架的流程,直接看核心代码就行!
ApplicationEvent和Listener实际上就是Spring基于观察者模式设计的发布-订阅事件模型。
ApplicationEvent :自定义的事件对象,用于表示具体的事件。
具体代码如下:
import org.springframework.context.ApplicationEvent;
public class CSDN extends ApplicationEvent {
private String message;
public CSDN(Object source,String message) {
super(source);
this.message = message;
}
public String getMessage(){
return this.message;
}
public void setMessage(String message){
this.message = message;
}
}
ApplicationListener:事件监听器接口,用于监听特定事件:
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
@Component
public class JackFans implements ApplicationListener {
@Override
public void onApplicationEvent(CSDN event) {
System.out.println("Jack收到私信:"+event.getMessage());
}
}
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
@Component
public class TomFans implements ApplicationListener {
@Override
public void onApplicationEvent(CSDN event) {
System.out.println("Tom收到私信:"+event.getMessage());
}
}
单元测试:
ApplicationContext:Spring上下文,用于广播ApplicationEvent,并通知相关的ApplicationListener。
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.context.ApplicationContext;
import javax.annotation.Resource;
@SpringBootTest
class ObserverApplicationTests {
@Resource
private ApplicationContext applicationContext;
@Test
void contextLoads() {
CSDN csdn = new CSDN(this,"发布了《关于整顿社区的通知》");
applicationContext.publishEvent(csdn);
}
}
运行结果如下:
通过这种方式,不同的业务逻辑模块就可以不依赖于具体的实现,只通过监听特定事件来响应,从而实现解耦。
事件驱动的编程方式还有以下优点:
1、降低耦合度
2、提高模块间的独立性和可重用性
3、易于扩展和维护
总的来说,ApplicationEvent和Listener的设计初衷就是为了解耦和提高系统的扩展性、稳定性。
优点:
1、观察者和被观察者解耦,增强了灵活性。
2、符合开闭原则,容易扩展。
3、支持广播通信,一个对象状态变化会通知多个观察者对象。
4、建立一套触发机制。
缺点:
1、如果观察者很多,通知的开销很大。
2、被观察者发送通知,无法知道有哪些观察者处理。
应用场景:
1、需要一对多依赖关系的场景,一个对象状态变化需要通知其他多个对象。
2、跨系统的消息交换,如消息队列的生产者和消费者。
3、事件驱动型程序,如按钮点击触发的响应。
符合的设计原则:
1、单一职责原则(Single Responsibility Principle)
观察者和被观察者职责明确区分,都仅负责自己的功能。
2、开闭原则(Open Closed Principle)
可以新增观察者而不影响被观察者,扩展开放。
3、里氏替换原则(Liskov Substitution Principle)
观察者都遵循统一接口,扩展观察者不会对系统造成影响。
4、依赖倒转原则(Dependency Inversion Principle)
被观察者和观察者都依赖于抽象接口,不依赖具体实现。
5、接口隔离原则(Interface Segregation Principle)
观察者接口只定义了更新接口,避免了冗余。
综上,观察者模式建立一套触发机制和依赖关系,用于被观察对象状态变化时自动通知观察者,是一对多通信的最佳设计模式。