发布订阅者模式
前言
在Java中,发布和订阅者模式(Publish-Subscribe Pattern)是一种行为设计模式,它允许对象(称为发布者)发送消息(称为事件)给多个感兴趣的接收者(称为订阅者),而不需要知道这些接收者的具体细节。这种模式解耦了发布者和订阅者之间的直接联系,使得它们可以独立地变化和发展;
发布订阅者模式优点
解耦:发布者和订阅者之间不直接通信,它们通过中介(如事件总线)进行交互。这种解耦使得发布者和订阅者可以独立地改变和扩展,而不会影响到对方。例如,在软件开发中,如果你更改了发布事件的逻辑,但只要不改变事件的名称和格式,那么订阅这些事件的代码就不需要修改;
灵活性:一个发布者可以有多个订阅者,同时一个订阅者也可以订阅多个发布者发布的事件。这种灵活性使得系统能够轻松地处理复杂的交互场景;
扩展性:由于发布者和订阅者之间的解耦,向系统中添加新的订阅者或发布者变得非常简单。你不需要修改现有的代码,只需要将新的订阅者注册到中介上,或将新的发布者连接到中介上即可;
模拟实现
电视总台(发布者)
喜剧节目观众(订阅者)
恐怖节目观众(订阅者)
实现“电视总台”发布影片清单(清单包含了多种影片类型如喜剧、恐怖类),使用发布订阅者模式系统自动将对应影片分类发送给订阅了相应类型(喜剧/恐怖)的观众;如订阅了喜剧栏目的观众将收到喜剧栏目的影片推送。订阅了恐怖栏目的用户将收到恐怖栏目的影片推送;
代码片段
1.常量类
/*** 常量类*/
public class Constant {// 喜剧节目public static final Integer COMEDY_CHANNEL = 1;// 恐怖节目public static final Integer TERROR_CHANNEL = 2;
}
2.事件类
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.context.ApplicationEvent;
import java.util.List;
/*** 定义事件(继承ApplicationEvent类)* 节目事件*/
@Data
@Builder
public class ProgramEvent extends ApplicationEvent {// 节目清单private List<Program> programList;// 构造方法public ProgramEvent(Object source) {super(source);}// 构造方法public ProgramEvent(List<Program> programList) {super("");this.programList = programList;}/*** 节目实体 - 内部类*/@Data@AllArgsConstructor@NoArgsConstructorpublic static class Program{private String id; // 节目idprivate String programName; // 节目名称private Integer channel; // 节目分类(1: 喜剧 2:恐怖)}
}
3.喜剧/恐怖订阅者
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
import java.util.List;
import static fw.client.utils.designmode.listener.Constant.COMEDY_CHANNEL;
/*** 定义监听器(订阅者)(实现ApplicationListener方法)* 喜剧节目观众订阅者(当电视台发布喜剧节目时 喜剧节目观众订阅者会获取到发布的喜剧节目);*/
@Component
public class ComedyAudienceListener implements ApplicationListener<ProgramEvent> {/*** 当ProgramEvent发布就会执行 onApplicationEvent 方法;*/@Overridepublic void onApplicationEvent(ProgramEvent event) {// 获取电视台推送的所有节目清单List<ProgramEvent.Program> programList = event.getProgramList();programList.stream().filter(program -> COMEDY_CHANNEL == program.getChannel()).forEach(program -> System.out.println("喜剧节目观众接收到电视台推送的喜剧节目:" + program.getProgramName()));}
}
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
import java.util.List;
import static fw.client.utils.designmode.listener.Constant.TERROR_CHANNEL;
/*** 定义监听器(订阅者)(实现ApplicationListener方法)* 恐怖节目观众订阅者(当电视台发布恐怖节目时 恐怖节目订阅者会获取到发布的恐怖节目);*/
@Component
public class TerrorAudienceListener implements ApplicationListener<ProgramEvent> {/*** 当ProgramEvent发布就会执行 onApplicationEvent 方法;*/@Overridepublic void onApplicationEvent(ProgramEvent event) {// 获取电视台推送的所有节目清单List<ProgramEvent.Program> programList = event.getProgramList();programList.stream().filter(program -> TERROR_CHANNEL == program.getChannel()).forEach(program -> System.out.println("恐怖节目观众接收到电视台推送的恐怖节目:" + program.getProgramName()));}
}
4.事件广播器
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.ApplicationEventPublisherAware;
import org.springframework.stereotype.Component;
/*** 定义事件广播器* 通过调用publish方法传入事件。对应订阅(监听)事件器会获取到传入publish数据;*/
@Component
public class EventPublishUtil implements ApplicationEventPublisherAware {private static ApplicationEventPublisher applicationEventPublisher;@Overridepublic void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {EventPublishUtil.applicationEventPublisher = applicationEventPublisher;}/*** 广播事件*/public static void publish(ApplicationEvent event){applicationEventPublisher.publishEvent(event);}
}
5.发布者
使用SpringBoot的测试方法模式发布者;
import fw.client.utils.designmode.listener.EventPublishUtil;
import fw.client.utils.designmode.listener.ProgramEvent;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.ArrayList;
import java.util.List;
/*** 发布订阅者模式*/
@SpringBootTest
public class ListenerMdoeTest {/*** TV - 发布者*/@Testpublic void TVPublisher() {// 构造喜剧节目ProgramEvent.Program comedyProgram1 = new ProgramEvent.Program("A1","唐伯虎点秋香",1);ProgramEvent.Program comedyProgram2 = new ProgramEvent.Program("A2","大话西游",1);// 构造恐怖节目ProgramEvent.Program terrorProgram1 = new ProgramEvent.Program("B1","山村老尸",2);// 构造节目清单List<ProgramEvent.Program> programsList = new ArrayList<>();programsList.add(comedyProgram1);programsList.add(comedyProgram2);programsList.add(terrorProgram1);// 发布电视节目清单EventPublishUtil.publish(new ProgramEvent(programsList));}
}
测试结果
# 控制台输出
喜剧节目观众接收到电视台推送的喜剧节目:唐伯虎点秋香
喜剧节目观众接收到电视台推送的喜剧节目:大话西游
恐怖节目观众接收到电视台推送的恐怖节目:山村老尸
补充说明
发布订阅者模式扩展性很好,假如此时新增加一个影片类型如(冒险类)。我们只需要增加一个新的订阅者去处理这部分数据即可。对于“发布者”方代码来说无需做任何处理;