观察者模式
它有一种广为人知的名字发布订阅模式
1、简介:
- 对象之间存在一对多的依赖,当一个对象(一)状态发生改变时,其所依赖的对象(观察者对象)会自动收到通知。
- 被观察者:被依赖的对象
- 行为型设计模式
- 观察者:依赖的对象
- 监听器基于此原理
- 在实际的项目开发中,这两种对象的称呼是比较灵活的,有各种不同的叫法,比如:Subject-Observer、Publisher-Subscriber、Producer-Consumer、EventEmitter-EventListener、Dispatcher-Listener。
- 实现机制;
- 被观察类:这个类中有一个 ArrayList 存放观察者们,除此以外还应有类状态和设置和获取状态的方法,状态改变时通知所有观察者,
- 观察者类可以有个抽象类,所有的观察者类继承这个抽象类,观察者类有它要观察的对象。
- 多种实现方式:
- 进程内的
- 同步阻塞(BIO):观察者和被观察者代码再同一个线程内执行,被观察者一直阻塞,直到所有的观察者代码都执行完成之后,才执行后续的代码。
- 异步非阻塞
- 跨进程:PRC调用或者基于消息队列来实现
- 进程内的
2、模板代码:
2.1、demo1
//被观察者
public interface Subject {
void registerObserver(Observer observer);
void removeObserver(Observer observer);
void notifyObservers(Message message);
}
public interface Observer {
void update(Message message);
}
//被观察者 存放观察者集合,唤醒通知观察者方法,注册、移除观察者、
public class ConcreteSubject implements Subject {
private List<Observer> observers = new ArrayList<Observer>();
@Override
public void registerObserver(Observer observer) {
observers.add(observer);
}
@Override
public void removeObserver(Observer observer) {
observers.remove(observer);
}
@Override
public void notifyObservers(Message message) {
for (Observer observer : observers) {
observer.update(message);
}
}
}
public class ConcreteObserverOne implements Observer {
@Override
public void update(Message message) {
//TODO: 获取消息通知,执行自己的逻辑...
System.out.println("ConcreteObserverOne is notified.");
}
}
public class ConcreteObserverTwo implements Observer {
@Override
public void update(Message message) {
//TODO: 获取消息通知,执行自己的逻辑...
System.out.println("ConcreteObserverTwo is notified.");
}
}
public class Demo {
public static void main(String[] args) {
ConcreteSubject subject = new ConcreteSubject();
subject.registerObserver(new ConcreteObserverOne());
subject.registerObserver(new ConcreteObserverTwo());
subject.notifyObservers(new Message());
}
}
public class UserController {
private UserService userService; // 依赖注入
private PromotionService promotionService; // 依赖注入
public Long register(String telephone, String password) {
//省略输入参数的校验代码
//省略userService.register()异常的try-catch代码
long userId = userService.register(telephone, password);
promotionService.issueNewUserExperienceCash(userId);
return userId;
}
}
2.2、用户注册
//具体服务类
public class NotificationService {
public void sendInboxMessage(Long userId,String msg){
System.err.println("通知观察者在发送欢迎通知");
}
}
public class PromotionService {
public void issueNewUserExperienceCash(Long userId){
System.err.println("新用户体验金来了");
}
}
/**
* 注册观察者
*/
public interface RegObserver {
void handleRegSuccess(Long userId);
}
public class RegNotificationObserver implements RegObserver {
private NotificationService notificationService=new NotificationService();
@Override
public void handleRegSuccess(Long userId) {
notificationService.sendInboxMessage(userId, "Welcome...");
}
}
public class RegPromotionObserver implements RegObserver {
private PromotionService promotionService=new PromotionService() ; // 依赖注入
@Override
public void handleRegSuccess(Long userId) {
promotionService.issueNewUserExperienceCash(userId);
}
}
public class UserService {
public Long register(String telephone, String password) {
return 101L;
}
}
public class UserController {
private UserService userService=new UserService();
private static List<RegObserver> regObservers = new ArrayList<>();
//多种形式: set/static/反射
static {
regObservers.add(new RegNotificationObserver());
regObservers.add(new RegPromotionObserver());
}
public void setRegObservers(List<RegObserver> observers){
regObservers.addAll(observers);
}
public Long register(String telephone,String password){
//参数校验
Long userId= userService.register(telephone,password);
//可开启线程
for (RegObserver regObserver: regObservers ) {
regObserver.handleRegSuccess(userId);
}
return userId;
}
public static void main(String[] args) {
UserController userController = new UserController();
// List<RegObserver> list = new ArrayList<>();
// list.add(new RegNotificationObserver());
// list.add(new RegPromotionObserver());
// userController.setRegObservers(list);
Long register =userController.register("123", "123");
System.err.println(register);
}
}
//反射工具类
public class ClassUtil {
private static Logger logger=Logger.getLogger(ClassUtil.class.getName());
/**
* 获取所有接口的实现类
* 如果是A.isAssignableFrom(B) 确定一个类(B)是不是继承来自于另一个父类(A),一个接口(A)是不是实现了另外一个接口(B),或者两个类相同。
* 主要,这里比较的维度不是实例对象,而是类本身,因为这个方法本身就是Class类的方法,判断的肯定是和类信息相关的。
* 也就是判断当前的Class对象所表示的类,
* 是不是参数中传递的Class对象所表示的类的父类,超接口,或者是相同的类型。是则返回true,否则返回false。
* @return
*/
public static List<Class> getImplByInterface(Class c){
List<Class> classes = new ArrayList<>();
//判断是否为接口
if(!c.isInterface()){
logger.warning("当前类不是接口");
return null;
}else{
//获得当前包名
String packageName = c.getPackage().getName();
try {
//获得当前包下所有类
List<Class> allClass=getAllClassByCurrentPackage(packageName);
//遍历判断
for (Class ck:allClass) {
String className = ck.getName().substring(ck.getName().lastIndexOf(".") + 1);
//排除抽象类
if(Modifier.isAbstract(ck.getModifiers())){
logger.info("当前类:"+className+"为抽象类");
continue;
}
//判断是否为当前接口的子类或者本接口
if(!c.isAssignableFrom(ck)){//不是
logger.info("当前类:"+className+"不是当前接口的实现类");
continue;
}else {
//排除自己
if(c.equals(ck)){
logger.info("当前类"+className+"不可保留,必须排除");
continue;
}else {
classes.add(ck);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
return classes;
}
/**
* 获得当前包下所有类,在jar包中不能查找
* @param packageName
* @return
*/
private static List<Class> getAllClassByCurrentPackage(String packageName) throws IOException, ClassNotFoundException {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
String path=packageName.replace(".","/");
Enumeration<URL> resources = loader.getResources(path);
List<File> dirs = new ArrayList<>();
while (resources.hasMoreElements()){
URL url = resources.nextElement();
dirs.add(new File(url.getFile()));
}
List<Class> classes = new ArrayList<>();
for (File dir :dirs) {
classes.addAll(findClasses(dir,packageName));
}
return classes;
}
private static List<Class> findClasses(File dir, String packageName) throws ClassNotFoundException {
List<Class> classes = new ArrayList<>();
if (!dir.exists()) {//文件不存在
return classes;
}
// listFiles 存放的是此文件夹下的文件路径+文件名(包含父级目录的完整路径)
File[] files = dir.listFiles();
for (File file : files) {
if (file.isDirectory()) {//文件是目录,递归遍历
//递归查找文件夹【即对应的包】下面的所有文件
assert !file.getName().contains(".");
/**
* J2SE 1.4在语言上提供了一个新特性,就是assertion(断言)功能,它是该版本在Java语言方面最大的革新。在软件开发中,assertion是一种经典的调试、测试方式。
在语法上,为了支持assertion,Java增加了一个关键字assert。它包括两种表达式,分别如下:
assert expression1; 当为true时,程序正常执行,false时抛出异常终止程序。
assert expression1: expression2; 如果表达式1为true时,程序正常运行,如过为false则抛出异常,终止程序,并将表达式2的内容随异常信息一起打印。
在两种表达式中,expression1表示一个boolean表达式,
expression2表示一个基本类型或者是一个对象(Object) ,基本类型包括boolean,char,double,float,int和long。
由于所有类都为Object的子类,因此这个参数可以用于所有对象。
assert如果为true,则程序继续执行。如果为false,则程序抛出AssertionError,并终止执行。
*/
// 递归
classes.addAll(findClasses(file, packageName + '.' + file.getName()));
} else if (file.getName().endsWith(".class")) {
classes.add(Class.forName(packageName + "." + file.getName().substring(0,file.getName().length() - 6)));
}
}
return classes;
}
}
import top.jkxljc.strategy.demo2.StringUtils;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
/**
* @Description: 反射工具类
* @Auther: wuzhazha
*/
public class ReflectionUtil {
/**
* 定义类集合(用于存放所有加载的类)
*/
private static Set<Class<?>> CLASS_SET;
// static {
// //指定加载包路径
// CLASS_SET = getClassSet("com.yaolong");
// }
//
/**
* 获取类加载器
* @return
*/
public static ClassLoader getClassLoader(){
return Thread.currentThread().getContextClassLoader();
}
/**
* 加载类
* @param className 类全限定名称
* @param isInitialized 是否在加载完成后执行静态代码块
* @return
*/
public static Class<?> loadClass(String className,boolean isInitialized) {
Class<?> cls;
try {
cls = Class.forName(className,isInitialized,getClassLoader());
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
return cls;
}
public static Class<?> loadClass(String className) {
return loadClass(className,true);
}
/**
* 获取指定包下所有类
* @param packageName
* @return
*/
public static Set<Class<?>> getClassSet(String packageName) {
Set<Class<?>> classSet = new HashSet<>();
try {
Enumeration<URL> urls = getClassLoader().getResources(packageName.replace(".","/"));
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
if (url != null) {
String protocol = url.getProtocol();
if (protocol.equals("file")) {
String packagePath = url.getPath().replace("%20","");
addClass(classSet,packagePath,packageName);
} else if (protocol.equals("jar")) {
JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
if (jarURLConnection != null) {
JarFile jarFile = jarURLConnection.getJarFile();
if (jarFile != null) {
Enumeration<JarEntry> jarEntries = jarFile.entries();
while (jarEntries.hasMoreElements()) {
JarEntry jarEntry = jarEntries.nextElement();
String jarEntryName = jarEntry.getName();
if (jarEntryName.endsWith(".class")) {
String className = jarEntryName.substring(0, jarEntryName.lastIndexOf(".")).replaceAll("/", ".");
doAddClass(classSet,className);
}
}
}
}
}
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
return classSet;
}
private static void doAddClass(Set<Class<?>> classSet, String className) {
Class<?> cls = loadClass(className,false);
classSet.add(cls);
}
private static void addClass(Set<Class<?>> classSet, String packagePath, String packageName) {
final File[] files = new File(packagePath).listFiles(new FileFilter() {
@Override
public boolean accept(File file) {
return (file.isFile() && file.getName().endsWith(".class")) || file.isDirectory();
}
});
for (File file : files) {
String fileName = file.getName();
if (file.isFile()) {
String className = fileName.substring(0, fileName.lastIndexOf("."));
if (StringUtils.isNotEmpty(packageName)) {
className = packageName + "." + className;
}
doAddClass(classSet,className);
} else {
String subPackagePath = fileName;
if (StringUtils.isNotEmpty(packagePath)) {
subPackagePath = packagePath + "/" + subPackagePath;
}
String subPackageName = fileName;
if (StringUtils.isNotEmpty(packageName)) {
subPackageName = packageName + "." + subPackageName;
}
addClass(classSet,subPackagePath,subPackageName);
}
}
}
public static Set<Class<?>> getClassSet() {
CLASS_SET= getClassSet(ReflectionUtil.class.getPackage().getName());
return CLASS_SET;
}
/**
* 获取应用包名下某父类(或接口)的所有子类(或实现类)
* @param superClass
* @return
*/
public static Set<Class<?>> getClassSetBySuper(Class<?> superClass) {
return getClassSetBySuper(superClass, ReflectionUtil.class.getPackage().getName());
}
public static Set<Class<?>> getClassSetBySuper(Class<?> superClass,String packageName) {
CLASS_SET= getClassSet(packageName);
Set<Class<?>> classSet = new HashSet<>();
for (Class<?> cls : CLASS_SET) {
if (superClass.isAssignableFrom(cls) && !superClass.equals(cls)) {
classSet.add(cls);
}
}
return classSet;
}
/**
* 获取应用包名下带有某注解的类
* @param annotationClass
* @return
*/
public static Set<Class<?>> getClassSetByAnnotation(Class<? extends Annotation> annotationClass) {
CLASS_SET= getClassSet(ReflectionUtil.class.getPackage().getName());
Set<Class<?>> classSet = new HashSet<>();
for (Class<?> cls : CLASS_SET) {
//是否存在此注解
if (cls.isAnnotationPresent(annotationClass)) {
classSet.add(cls);
}
}
return classSet;
}
}
- 此时需要添加新的观察者,只需要增加一个类实现观察者接口就行,不用修改别的代码(用户注册成功之后,推送用户注册信息给大数据征信系统)
- 优化代码(同步阻塞变为异步非阻塞):当 userService.register() 函数执行完成之后,我们启动一个新的线程来执行观察者的 handleRegSuccess() 函数,这样 userController.register() 函数就不需要等到所有的 handleRegSuccess() 函数都执行完成之后才返回结果给客户端--------userController.register() 函数从执行 3 个 SQL 语句才返回,减少到只需要执行 1 个 SQL 语句就返回,响应时间粗略来讲减少为原来的 1/3。本质在进程内
- 再优化跨进程:
- 大数据征信系统提供了发送用户注册信息的 RPC 接口,我们仍然可以沿用之前的实现思路,在 handleRegSuccess() 函数中调用 RPC 接口来发送数据。
- 基于消息队列(Message Queue,比如 ActiveMQ)来实现。
- 优势:
- 被观察者和观察者解耦更加彻底,两部分的耦合更小
- 被观察者完全不感知观察者,同理,观察者也完全不感知被观察者。
- 被观察者只管发送消息到消息队列,观察者只管从消息队列中读取消息来执行相应的逻辑。
- 优势:
3、优缺点:
创建型:将创建和使用代码解耦
结构型:将不同功能代码解耦,
行为型:不同的行为代码解耦,具体到观察者模式,它是将观察者和被观察者代码解耦
优点:
- 观察者和观察者抽象耦合(观察者和被观察者代码解耦)
- 建立一套触发机制
缺点:
- 1、如果一个被观察者对象有很多的直接和间接的观察者的话,将所有的观察者都通知到会花费很多时间。
- 2、如果在观察者和观察目标之间有循环依赖的话,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
- 3、观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
4、应用场景:
小到代码层面的解耦,大到架构层面的系统解耦,再或者一些产品的设计思路。
- 邮件订阅
- RSS fEEDS
- 微信公众号
- 支付场景:
- 用户购买一件商品,当支付成功之后三方会回调自身,在这个时候系统可能会有很多需要执行的逻辑(如:更新订单状态,发送邮件通知,赠送礼品…),这些逻辑之间并没有强耦合,因此天然适合使用观察者模式去实现这些功能,当有更多的操作时,只需要添加新的观察者就能实现,完美实现了对修改关闭,对扩展开放的开闭原则。
- UGC(用户原创内容)场景
- 在一个UGC场景下,用户发布的内容往往会经过很多流程,大部分是先发往审核系统,当审核通过之后就会出现一系列的业务逻辑,比如更新内容状态,通知给所有的粉丝等等,而这些业务逻辑就可以作为观察者。