行为型模式用于描述程序在运行时复杂的流程控制,即描述多个类或对象之间怎样相互协作共同完成单个对象都无法单独完成的任务,它涉及算法与对象间职责的分配。
行为型模式分为类行为模式和对象行为模式,前者采用继承机制来在类间分派行为,后者采用组合或聚合在对象间分配行为。由于组合关系或聚合关系比继承关系耦合度低,满足“合成复用原则”,所以对象行为模式比类行为模式具有更大的灵活性。
行为型模式分为:
以上 11 种行为型模式,除了模板方法模式和解释器模式是类行为型模式,其他的全部属于对象行为型模式。
观察者模式的应用场景非常广泛,小到代码层面的解耦,大到架构层面的系统解耦,再或者 一些产品的设计思路,都有这种模式的影子.
现在我们常说的基于事件驱动的架构,其实也是观察者模式的一种最佳实践。当我们观察某一个对象时,对象传递出的每一个行为都被看成是一个事件,观察者通过处理每一个事件来完成自身的操作处理。
生活中也有许多观察者模式的应用,比如 汽车与红绿灯的关系,‘红灯停,绿灯行’,在这个过程中交通信号灯是汽车的观察目标,而汽车是观察者.
观察者模式(observer pattern)的原始定义是:定义对象之间的一对多依赖关系,这样当一个对象改变状态时,它的所有依赖项都会自动得到通知和更新。
解释一下上面的定义: 观察者模式它是用于建立一种对象与对象之间的依赖关系,一个对象发生改变时将自动通知其他对象,其他对象将相应的作出反应.
在观察者模式中发生改变的对象称为观察目标,而被通知的对象称为观察者,一个观察目标可以应对多个观察者,而且这些观察者之间可以没有任何相互联系,可以根据需要增加和删除观察者,使得系统更易于扩展.
观察者模式的别名有发布-订阅(Publish/Subscribe)模式,模型-视图(Model-View)模式、源-监听(Source-Listener) 模式等
观察者模式结构中通常包括: 观察目标和观察者两个继承层次结构.
在观察者模式中有如下角色:
/**
* 抽象观察者
* @author spikeCong
* @date 2022/10/11
**/
public interface Observer {
//update方法: 为不同观察者的更新(响应)行为定义相同的接口,不同的观察者对该方法有不同的实现
public void update();
}
/**
* 具体观察者
* @author spikeCong
* @date 2022/10/11
**/
public class ConcreteObserverOne implements Observer {
@Override
public void update() {
//获取消息通知,执行业务代码
System.out.println("ConcreteObserverOne 得到通知!");
}
}
/**
* 具体观察者
* @author spikeCong
* @date 2022/10/11
**/
public class ConcreteObserverTwo implements Observer {
@Override
public void update() {
//获取消息通知,执行业务代码
System.out.println("ConcreteObserverTwo 得到通知!");
}
}
/**
* 抽象目标类
* @author spikeCong
* @date 2022/10/11
**/
public interface Subject {
void attach(Observer observer);
void detach(Observer observer);
void notifyObservers();
}
/**
* 具体目标类
* @author spikeCong
* @date 2022/10/11
**/
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();
}
}
}
public class Client {
public static void main(String[] args) {
//创建目标类(被观察者)
ConcreteSubject subject = new ConcreteSubject();
//注册观察者类,可以注册多个
subject.attach(new ConcreteObserverOne());
subject.attach(new ConcreteObserverTwo());
//具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。
subject.notifyObservers();
}
}
接下来我们使用观察模式,来实现一个买房摇号的程序.摇号结束,需要通过短信告知用户摇号结果,还需要想MQ中保存用户本次摇号的信息.
1 ) 未使用设计模式
/**
* 模拟买房摇号服务
* @author spikeCong
* @date 2022/10/11
**/
public class DrawHouseService {
//摇号抽签
public String lots(String uId){
if(uId.hashCode() % 2 == 0){
return "恭喜ID为: " + uId + " 的用户,在本次摇号中中签! !";
}else{
return "很遗憾,ID为: " + uId + "的用户,您本次未中签! !";
}
}
}
public class LotteryResult {
private String uId; // 用户id
private String msg; // 摇号信息
private Date dataTime; // 业务时间
//get&set.....
}
/**
* 开奖服务接口
* @author spikeCong
* @date 2022/10/11
**/
public interface LotteryService {
//摇号相关业务
public LotteryResult lottery(String uId);
}
/**
* 开奖服务
* @author spikeCong
* @date 2022/10/11
**/
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());
}
}
@Test
public void test1(){
LotteryService ls = new LotteryServiceImpl();
String result = ls.lottery("1234567887654322");
System.out.println(result);
}
1 ) 使用观察者模式进行优化
上面的摇号业务中,摇号、发短信、发MQ消息是一个顺序调用的过程,但是除了摇号这个核心功能以外, 发短信与记录信息到MQ的操作都不是主链路的功能,需要单独抽取出来,这样才能保证在后面的开发过程中保证代码的可扩展性和可维护性.
/**
* 事件监听接口
* @author spikeCong
* @date 2022/10/11
**/
public interface EventListener {
void doEvent(LotteryResult result);
}
/**
* 短信发送事件
* @author spikeCong
* @date 2022/10/11
**/
public class MessageEventListener implements EventListener {
@Override
public void doEvent(LotteryResult result) {
System.out.println("发送短信通知用户ID为: " + result.getuId() +
",您的摇号结果如下: " + result.getMsg());
}
}
/**
* MQ消息发送事件
* @author spikeCong
* @date 2022/10/11
**/
public class MQEventListener implements EventListener {
@Override
public void doEvent(LotteryResult result) {
System.out.println("记录用户摇号结果(MQ), 用户ID:" + result.getuId() +
",摇号结果:" + result.getMsg());
}
}
事件处理
/**
* 事件处理类
* @author spikeCong
* @date 2022/10/11
**/
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);
}
}
}
摇号业务处理
/**
* 开奖服务接口
* @author spikeCong
* @date 2022/10/11
**/
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);
}
/**
* 开奖服务
* @author spikeCong
* @date 2022/10/11
**/
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());
}
}
测试
@Test
public void test2(){
LotteryService ls = new LotteryServiceImpl();
LotteryResult result = ls.lotteryAndMsg("1234567887654322");
System.out.println(result);
}
1) 观察者模式的优点
2) 观察者模式的缺点
3 ) 观察者模式常见的使用场景
4 ) JDK 中对观察者模式的支持
JDK中提供了Observable类以及Observer接口,它们构成了JDK对观察者模式的支持.
java.util.Observer
接口: 该接口中声明了一个方法,它充当抽象观察者,其中声明了一个update方法.
void update(Observable o, Object arg);
java.util.Observable
类: 充当观察目标类(被观察类) , 在该类中定义了一个Vector集合来存储观察者对象.下面是它最重要的 3 个方法。
void notifyObservers(Object arg) 方法:调用集合中的所有观察者对象的 update方法,通知它们数据发生改变。通常越晚加入集合的观察者越先得到通知。
void setChange() 方法:用来设置一个 boolean 类型的内部标志,注明目标对象发生了变化。当它为true时,notifyObservers() 才会通知观察者。
用户可以直接使用Observer接口和Observable类作为观察者模式的抽象层,再自定义具体观察者类和具体观察目标类,使用JDK中提供的这两个类可以更加方便的实现观察者模式.
模板方法模式(template method pattern)原始定义是:在操作中定义算法的框架,将一些步骤推迟到子类中。模板方法让子类在不改变算法结构的情况下重新定义算法的某些步骤。
模板方法中的算法可以理解为广义上的业务逻辑,并不是特指某一个实际的算法.定义中所说的算法的框架就是模板, 包含算法框架的方法就是模板方法.
例如: 我们去医院看病一般要经过以下4个流程:挂号、取号、排队、医生问诊等,其中挂号、 取号 、排队对每个病人是一样的,可以在父类中实现,但是具体医生如何根据病情开药每个人都是不一样的,所以开药这个操作可以延迟到子类中实现。
模板方法模式是一种基于继承的代码复用技术,它是一种类行为模式. 模板方法模式其结构中只存在父类与子类之间的继承关系.
模板方法的作用主要是提高程序的复用性和扩展性:
模板方法模式的定位很清楚,就是为了解决算法框架这类特定的问题,同时明确表示需要使用继承的结构。
模板方法(Template Method)模式包含以下主要角色:
抽象类(Abstract Class):负责给出一个算法的轮廓和骨架。它由一个模板方法和若干个基本方法构成。
模板方法:定义了算法的骨架,按某种顺序调用其包含的基本方法。
基本方法:是实现算法各个步骤的方法,是模板方法的组成部分。基本方法又可以分为三种:
抽象方法(Abstract Method) :一个抽象方法由抽象类声明、由其具体子类实现。
具体方法(Concrete Method) :一个具体方法由一个抽象类或具体类声明并实现,其子类可以进行覆盖也可以直接继承。
钩子方法(Hook Method) :在抽象类中已经实现,包括用于判断的逻辑方法和需要子类重写的空方法两种。
一般钩子方法是用于判断的逻辑方法,这类方法名一般为isXxx,返回值类型为boolean类型。
UML类图对应的代码实现
/**
* 抽象父类
* @author spikeCong
* @date 2022/10/12
**/
public abstract class AbstractClassTemplate {
void step1(String key){
System.out.println("在模板类中 -> 执行步骤1");
if(step2(key)){
step3();
}else{
step4();
}
step5();
}
boolean step2(String key){
System.out.println("在模板类中 -> 执行步骤2");
if("x".equals(key)){
return true;
}
return false;
}
abstract void step3();
abstract void step4();
void step5(){
System.out.println("在模板类中 -> 执行步骤5");
}
void run(String key){
step1(key);
}
}
public class ConcreteClassA extends AbstractClassTemplate{
@Override
void step3() {
System.out.println("在子类A中 -> 执行步骤 3");
}
@Override
void step4() {
System.out.println("在子类A中 -> 执行步骤 4");
}
}
public class ConcreteClassB extends AbstractClassTemplate {
@Override
void step3() {
System.out.println("在子类B中 -> 执行步骤 3");
}
@Override
void step4() {
System.out.println("在子类B中 -> 执行步骤 4");
}
}
public class Test01 {
public static void main(String[] args) {
AbstractClassTemplate concreteClassA = new ConcreteClassA();
concreteClassA.run("");
System.out.println("===========");
AbstractClassTemplate concreteClassB = new ConcreteClassB();
concreteClassB.run("x");
}
}
// 输出结果
在模板类中 -> 执行步骤1
在模板类中 -> 执行步骤2
在子类A中 -> 执行步骤 4
在模板类中 -> 执行步骤5
===========
在模板类中 -> 执行步骤1
在模板类中 -> 执行步骤2
在子类B中 -> 执行步骤 3
在模板类中 -> 执行步骤5
P2P公司的借款系统中有一个利息计算模块,利息的计算流程是这样的:
/**
* 账户抽象类
* @author spikeCong
* @date 2022/10/12
**/
public abstract class Account {
//step1 具体方法-验证用户信息是否正确
public boolean validate(String account,String password){
System.out.println("账号: " + account + ",密码: " + password);
if(account.equalsIgnoreCase("tom") &&
password.equalsIgnoreCase("123456")){
return true;
}else{
return false;
}
}
//step2 抽象方法-计算利息
public abstract void calculate();
//step3 具体方法-显示利息
public void display(){
System.out.println("显示利息!");
}
//模板方法
public void handle(String account,String password){
if(!validate(account,password)){
System.out.println("账户或密码错误!!");
return;
}
calculate();
display();
}
}
/**
* 借款一个月
* @author spikeCong
* @date 2022/10/12
**/
public class LoanOneMonth extends Account{
@Override
public void calculate() {
System.out.println("借款周期30天,利率为10%!");
}
}
/**
* 借款七天
* @author spikeCong
* @date 2022/10/12
**/
public class LoanSevenDays extends Account{
@Override
public void calculate() {
System.out.println("借款周期7天,无利息!仅收取贷款金额1%的服务费!");
}
@Override
public void display() {
System.out.println("七日内借款无利息!");
}
}
public class Client {
public static void main(String[] args) {
Account a1 = new LoanSevenDays();
a1.handle("tom","12345");
System.out.println("==========================");
Account a2 = new LoanOneMonth();
a2.handle("tom","123456");
}
}
优点:
在父类中形式化的定义一个算法,而由它的子类来实现细节处理,在子类实现详细的处理代码时,并不会改变父类算法中步骤的执行顺序.
模板方法可以实现一种反向的控制结构,通过子类覆盖父类的钩子方法,来决定某一个特定步骤是否需要执行
在模板方法模式中可以通过子类来覆盖父类的基本方法,不同的子类可以提供基本方法的不同实现,更换和增加新的子类很方便,符合单一职责原则和开闭原则.
缺点:
模板方法模式的使用场景一般有:
策略模式(strategy pattern)的原始定义是:定义一系列算法,将每一个算法封装起来,并使它们可以相互替换。策略模式让算法可以独立于使用它的客户端而变化。
其实我们在现实生活中常常遇到实现某种目标存在多种策略可供选择的情况,例如,出行旅游可以乘坐飞机、乘坐火车、骑自行车或自己开私家车等。
在软件开发中,经常会遇到这种情况,开发一个功能可以通过多个算法去实现,我们可以将所有的算法集中在一个类中,在这个类中提供多个方法,每个方法对应一个算法, 或者我们也可以将这些算法都封装在一个统一的方法中,使用if…else…等条件判断语句进行选择.但是这两种方式都存在硬编码的问题,后期需要增加算法就需要修改源代码,这会导致代码的维护变得困难.
比如网购,你可以选择工商银行、农业银行、建设银行等等,但是它们提供的算法都是一致的,就是帮你付款。
在软件开发中也会遇到相似的情况,当实现某一个功能存在多种算法或者策略,我们可以根据环境或者条件的不同选择不同的算法或者策略来完成该功能。
在策略模式中可以定义一些独立的类来封装不同的算法,每一个类封装一种具体的算法,在这里每一个封装算法的类都可以被称为一种策略,为了保证这些策略在使用时具有一致性,一般会提供一个抽象的策略类来做算法的声明.而每种算法对应一个具体的策略类.
策略模式的主要角色如下:
策略模式的本质是通过Context类来作为中心控制单元,对不同的策略进行调度分配。
/**
* 抽象策略类
* @author spikeCong
* @date 2022/10/13
**/
public interface Strategy {
void algorithm();
}
public class ConcreteStrategyA implements Strategy {
@Override
public void algorithm() {
System.out.println("执行策略A");
}
}
public class ConcreteStrategyB implements Strategy {
@Override
public void algorithm() {
System.out.println("执行策略B");
}
}
/**
* 环境类
* @author spikeCong
* @date 2022/10/13
**/
public class Context {
//维持一个对抽象策略类的引用
private Strategy strategy;
public Context(Strategy strategy) {
this.strategy = strategy;
}
//调用策略类中的算法
public void algorithm(){
strategy.algorithm();
}
}
public class Client {
public static void main(String[] args) {
Strategy strategyA = new ConcreteStrategyA();
Context context = new Context(strategyA); //可以在运行时指定类型,通过配置文件+反射机制实现
context.algorithm();
}
}
面试问题: 如何用设计模式消除代码中的if-else
物流行业中,通常会涉及到EDI报文(XML格式文件)传输和回执接收,每发送一份EDI报文,后续都会收到与之关联的回执(标识该数据在第三方系统中的流转状态)。
这里列举几种回执类型:MT1101、MT2101、MT4101、MT8104,系统在收到不同的回执报文后,会执行对应的业务逻辑处理。我们就业回执处理为演示案例
1 ) 不使用设计模式
/**
* 回执信息
* @author spikeCong
* @date 2022/10/13
**/
public class Receipt {
private String message; //回执信息
private String type; //回执类型(MT1101、MT2101、MT4101、MT8104)
public Receipt() {
}
public Receipt(String message, String type) {
this.message = message;
this.type = type;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
}
public class ReceiptBuilder {
public static List<Receipt> genReceiptList(){
//模拟回执信息
List<Receipt> receiptList = new ArrayList<>();
receiptList.add(new Receipt("MT1101回执","MT1011"));
receiptList.add(new Receipt("MT2101回执","MT2101"));
receiptList.add(new Receipt("MT4101回执","MT4101"));
receiptList.add(new Receipt("MT8104回执","MT8104"));
//......
return receiptList;
}
}
public class Client {
public static void main(String[] args) {
List<Receipt> receiptList = ReceiptBuilder.genReceiptList();
//循环判断
for (Receipt receipt : receiptList) {
if("MT1011".equals(receipt.getType())){
System.out.println("接收到MT1011回执!");
System.out.println("解析回执内容");
System.out.println("执行业务逻辑A"+"\n");
}else if("MT2101".equals(receipt.getType())){
System.out.println("接收到MT2101回执!");
System.out.println("解析回执内容");
System.out.println("执行业务逻辑B"+"\n");
}else if("MT4101".equals(receipt.getType())) {
System.out.println("接收到MT4101回执!");
System.out.println("解析回执内容");
System.out.println("执行业务逻辑C"+"\n");
}else if("MT8104".equals(receipt.getType())) {
System.out.println("接收到MT8104回执!");
System.out.println("解析回执内容");
System.out.println("执行业务逻辑D");
}
//......
}
}
}
2 ) 使用策略模式进行优化
通过策略模式, 将所有的if-else分支的业务逻辑抽取为各种策略类,让客户端去依赖策略接口,保证具体策略类的改变不影响客户端.
/**
* 回执处理策略接口
* @author spikeCong
* @date 2022/10/13
**/
public interface ReceiptHandleStrategy {
void handleReceipt(Receipt receipt);
}
public class Mt1011ReceiptHandleStrategy implements ReceiptHandleStrategy {
@Override
public void handleReceipt(Receipt receipt) {
System.out.println("解析报文MT1011: " + receipt.getMessage());
}
}
public class Mt2101ReceiptHandleStrategy implements ReceiptHandleStrategy {
@Override
public void handleReceipt(Receipt receipt) {
System.out.println("解析报文MT2101: " + receipt.getMessage());
}
}
......
/**
* 上下文类,持有策略接口
* @author spikeCong
* @date 2022/10/13
**/
public class ReceiptStrategyContext {
private ReceiptHandleStrategy receiptHandleStrategy;
public void setReceiptHandleStrategy(ReceiptHandleStrategy receiptHandleStrategy) {
this.receiptHandleStrategy = receiptHandleStrategy;
}
//调用策略类中的方法
public void handleReceipt(Receipt receipt){
if(receipt != null){
receiptHandleStrategy.handleReceipt(receipt);
}
}
}
public class ReceiptHandleStrategyFactory {
public ReceiptHandleStrategyFactory() {
}
//使用Map集合存储策略信息,彻底消除if...else
private static Map<String,ReceiptHandleStrategy> strategyMap;
//初始化具体策略,保存到map集合
public static void init(){
strategyMap = new HashMap<>();
strategyMap.put("MT1011",new Mt1011ReceiptHandleStrategy());
strategyMap.put("MT2101",new Mt2101ReceiptHandleStrategy());
}
//根据回执类型获取对应策略类对象
public static ReceiptHandleStrategy getReceiptHandleStrategy(String receiptType){
return strategyMap.get(receiptType);
}
}
public class Client {
public static void main(String[] args) {
//模拟回执
List<Receipt> receiptList = ReceiptBuilder.genReceiptList();
//策略上下文
ReceiptStrategyContext context = new ReceiptStrategyContext();
//策略模式将策略的 定义、创建、使用这三部分进行了解耦
for (Receipt receipt : receiptList) {
//获取置策略
ReceiptHandleStrategyFactory.init();
ReceiptHandleStrategy strategy = ReceiptHandleStrategyFactory.getReceiptHandleStrategy(receipt.getType());
//设置策略
context.setReceiptHandleStrategy(strategy);
//执行策略
context.handleReceipt(receipt);
}
}
}
经过上面的改造,我们已经消除了if-else的结构,每当新来了一种回执,只需要添加新的回执处理策略,并修改ReceiptHandleStrategyFactory中的Map集合。如果要使得程序符合开闭原则,则需要调整ReceiptHandleStrategyFactory中处理策略的获取方式,通过反射的方式,获取指定包下的所有IReceiptHandleStrategy实现类,然后放到字典Map中去.
1) 策略模式优点:
策略类之间可以自由切换
由于策略类都实现同一个接口,所以使它们之间可以自由切换。
易于扩展
增加一个新的策略只需要添加一个具体的策略类即可,基本不需要改变原有的代码,符合“开闭原则“
避免使用多重条件选择语句(if else),充分体现面向对象设计思想。
2) 策略模式缺点:
3) 策略模式使用场景
一个系统需要动态地在几种算法中选择一种时,可将每个算法封装到策略类中。
策略模式最大的作用在于分离使用算法的逻辑和算法自身实现的逻辑,这样就意味着当我们想要优化算法自身的实现逻辑时就变得非常便捷,一方面可以采用最新的算法实现逻辑,另一方面可以直接弃用旧算法而采用新算法。使用策略模式能够很方便地进行替换。
一个类定义了多种行为,并且这些行为在这个类的操作中以多个条件语句的形式出现,可将每个条件分支移入它们各自的策略类中以代替这些条件语句。
在实际开发中,有许多算法可以实现某一功能,如查找、排序等,通过 if-else 等条件判断语句来进行选择非常方便。但是这就会带来一个问题:当在这个算法类中封装了大量查找算法时,该类的代码就会变得非常复杂,维护也会突然就变得非常困难。虽然策略模式看上去比较笨重,但实际上在每一次新增策略时都通过新增类来进行隔离,短期虽然不如直接写 if-else 来得效率高,但长期来看,维护单一的简单类耗费的时间其实远远低于维护一个超大的复杂类。
系统要求使用算法的客户不应该知道其操作的数据时,可使用策略模式来隐藏与算法相关的数据结构。
如果我们不希望客户知道复杂的、与算法相关的数据结构,在具体策略类中封装算法与相关数据结构,可以提高算法的保密性与安全性.
设计原则和思想其实比设计模式更加的普适和重要,掌握了代码的设计原则和思想,我们自然而然的就可以使用到设计模式,还有可能自己创建出一种新的设计模式.
职责链模式(chain of responsibility pattern) 定义: 避免将一个请求的发送者与接收者耦合在一起,让多个对象都有机会处理请求.将接收请求的对象连接成一条链,并且沿着这条链传递请求,直到有一个对象能够处理它为止.
在职责链模式中,多个处理器(也就是刚刚定义中说的“接收对象”)依次处理同一个请 求。一个请求先经过 A 处理器处理,然后再把请求传递给 B 处理器,B 处理器处理完后再 传递给 C 处理器,以此类推,形成一个链条。链条上的每个处理器各自承担各自的处理职 责,所以叫作职责链模式。
职责链模式结构
职责链模式主要包含以下角色:
责任链模式的实现非常简单,每一个具体的处理类都会保存在它之后的下一个处理类。当处理完成后,就会调用设置好的下一个处理类,直到最后一个处理类不再设置下一个处理类,这时处理链条全部完成。
public class RequestData {
private String data;
public RequestData(String data) {
this.data = data;
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
}
/**
* 抽象处理者类
* @author spikeCong
* @date 2022/10/14
**/
public abstract class Handler {
protected Handler successor = null;
public void setSuccessor(Handler successor){
this.successor = successor;
}
public abstract void handle(RequestData requestData);
}
public class HandlerA extends Handler {
@Override
public void handle(RequestData requestData) {
System.out.println("HandlerA 执行代码逻辑! 处理: " + requestData.getData());
requestData.setData(requestData.getData().replace("A",""));
if(successor != null){
successor.handle(requestData);
}else{
System.out.println("执行中止!");
}
}
}
public class HandlerB extends Handler {
@Override
public void handle(RequestData requestData) {
System.out.println("HandlerB 执行代码逻辑! 处理: " + requestData.getData());
requestData.setData(requestData.getData().replace("B",""));
if(successor != null){
successor.handle(requestData);
}else{
System.out.println("执行中止!");
}
}
}
public class HandlerC extends Handler {
@Override
public void handle(RequestData requestData) {
System.out.println("HandlerC 执行代码逻辑! 处理: " + requestData.getData());
requestData.setData(requestData.getData());
if(successor != null){
successor.handle(requestData);
}else{
System.out.println("执行中止!");
}
}
}
public class Client {
public static void main(String[] args) {
Handler h1 = new HandlerA();
Handler h2 = new HandlerB();
Handler h3 = new HandlerC();
h1.setSuccessor(h2);
h2.setSuccessor(h3);
RequestData requestData = new RequestData("请求数据ABCDE");
h1.handle(requestData);
}
}
接下来我们模拟有一个双11期间,业务系统审批的流程,临近双十一公司会有陆续有一些新的需求上线,为了保证线上系统的稳定,我们对上线的审批流畅做了严格的控制.审批的过程会有不同级别的负责人加入进行审批(平常系统上线只需三级负责人审批即可,双十一前后需要二级或一级审核人参与审批),接下来我们就使用职责链模式来设计一下此功能.
1) 不使用设计模式
/**
* 审核信息
* @author spikeCong
* @date 2022/10/14
**/
public class AuthInfo {
private String code;
private String info ="";
public AuthInfo(String code, String... infos) {
this.code = code;
for (String str : infos) {
info = this.info.concat(str +" ");
}
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getInfo() {
return info;
}
public void setInfo(String info) {
this.info = info;
}
@Override
public String toString() {
return "AuthInfo{" +
"code='" + code + '\'' +
", info='" + info + '\'' +
'}';
}
}
/**
* 模拟审核服务
* @author spikeCong
* @date 2022/10/14
**/
public class AuthService {
//审批信息 审批人Id+申请单Id
private static Map<String,Date> authMap = new HashMap<String, Date>();
/**
* 审核流程
* @param uId 审核人id
* @param orderId 审核单id
*/
public static void auth(String uId, String orderId){
System.out.println("进入审批流程,审批人ID: " + uId);
authMap.put(uId.concat(orderId),new Date());
}
//查询审核结果
public static Date queryAuthInfo(String uId, String orderId){
return authMap.get(uId.concat(orderId)); //key=审核人id+审核单子id
}
}
public class AuthController {
//审核接口
public AuthInfo doAuth(String name, String orderId, Date authDate) throws ParseException {
//三级审批
Date date = null;
//查询是否存在审核信息,查询条件: 审核人ID+订单ID,返回Map集合中的Date
date = AuthService.queryAuthInfo("1000013", orderId);
//如果为空,封装AuthInfo信息(待审核)返回
if(date == null){
return new AuthInfo("0001","单号: "+orderId,"状态: 等待三级审批负责人进行审批");
}
//二级审批
SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");// 时间格式化
//二级审核人主要审核双十一之前, 11-01 ~ 11-10号的请求,所以要对传入的审核时间进行判断
//审核时间 大于 2022-11-01 并且 小于 2022-11-10,Date1.after(Date2),当Date1大于Date2时,返回TRUE,Date1.before(Date2),当Date1小于Date2时,返回TRUE
if(authDate.after(f.parse("2022-11-01 00:00:00")) && authDate.before(f.parse("2022-11-10 00:00:00"))){
//条件成立,查询二级审核的审核信息
date = AuthService.queryAuthInfo("1000012",orderId);
//如果为空,还是待二级审核人审核状态
if(date == null){
return new AuthInfo("0001","单号: "+orderId,"状态: 等待二级审批负责人进行审批");
}
}
//一级审批
//审核范围是在11-11日 ~ 11-31日
if(authDate.after(f.parse("2022-11-11 00:00:00")) && authDate.before(f.parse("2022-11-31 00:00:00"))){
date = AuthService.queryAuthInfo("1000011",orderId);
if(date == null){
return new AuthInfo("0001","单号: "+orderId,"状态: 等待一级审批负责人进行审批");
}
}
return new AuthInfo("0001","单号: "+orderId,"申请人:"+ name +", 状态: 审批完成!");
}
}
public class Client {
public static void main(String[] args) throws ParseException {
AuthController controller = new AuthController();
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date date = sdf.parse("2022-11-12 00:00:00");
//设置申请流程
//三级审核
//1.调用doAuth方法,模拟发送申请人相关信息
AuthInfo info1 = controller.doAuth("研发小周", "100001000010000", date);
System.out.println("当前审核状态: " + info1.getInfo());
/**
* 2.模拟进行审核操作, 虚拟审核人ID: 1000013
* 调用auth() 方法进行审核操作, 就是向Map中添加一个 审核人ID和申请单ID
*/
AuthService.auth("1000013", "100001000010000");
System.out.println("三级负责人审批完成,审批人: 王工");
System.out.println("===========================================================================");
//二级审核
//1.调用doAuth方法,模拟发送申请人相关信息
AuthInfo info2 = controller.doAuth("研发小周", "100001000010000", date);
System.out.println("当前审核状态: " + info2.getInfo());
/**
* 2.模拟进行审核操作, 虚拟审核人ID: 1000012
* 调用auth() 方法进行审核操作, 就是向Map中添加一个 审核人ID和申请单ID
*/
AuthService.auth("1000012", "100001000010000");
System.out.println("二级负责人审批完成,审批人: 张经理");
System.out.println("===========================================================================");
//一级审核
//1.调用doAuth方法,模拟发送申请人相关信息
AuthInfo info3 = controller.doAuth("研发小周", "100001000010000", date);
System.out.println("当前审核状态: " + info3.getInfo());
/**
* 2.模拟进行审核操作, 虚拟审核人ID: 1000012
* 调用auth() 方法进行审核操作, 就是向Map中添加一个 审核人ID和申请单ID
*/
AuthService.auth("1000011", "100001000010000");
System.out.println("一级负责人审批完成,审批人: 罗总");
}
}
2 ) 职责链模式重构代码
下图是为当前业务设计的责任链结构,统一抽象类AuthLink 下 有三个子类,将三个子类的执行通过编排,模拟出一条链路,这个链路就是业务中的责任链.
/**
* 抽象审核链类
*/
public abstract class AuthLink {
protected Logger logger = LoggerFactory.getLogger(AuthLink.class);
protected SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
protected String levelUserId; //审核人ID
protected String levelUserName; //审核人姓名
protected AuthLink next; //持有下一个处理类的引用
public AuthLink(String levelUserId, String levelUserName) {
this.levelUserId = levelUserId;
this.levelUserName = levelUserName;
}
//获取下一个处理类
public AuthLink getNext() {
return next;
}
//责任链中添加处理类
public AuthLink appendNext(AuthLink next) {
this.next = next;
return this;
}
//抽象审核方法
public abstract AuthInfo doAuth(String uId, String orderId, Date authDate);
}
/*
* 一级负责人
*/
public class Level1AuthLink extends AuthLink {
private Date beginDate = f.parse("2020-11-11 00:00:00");
private Date endDate = f.parse("2020-11-31 23:59:59");
public Level1AuthLink(String levelUserId, String levelUserName) throws ParseException {
super(levelUserId, levelUserName);
}
@Override
public AuthInfo doAuth(String uId, String orderId, Date authDate) {
Date date = AuthService.queryAuthInfo(levelUserId, orderId);
if (null == date) {
return new AuthInfo("0001", "单号:", orderId, " 状态:待一级审批负责人 ", levelUserName);
}
AuthLink next = super.getNext();
if (null == next) {
return new AuthInfo("0000", "单号:", orderId, " 状态:一级审批完成", " 时间:", f.format(date), " 审批人:", levelUserName);
}
if (authDate.before(beginDate) || authDate.after(endDate)) {
return new AuthInfo("0000", "单号:", orderId, " 状态:一级审批完成", " 时间:", f.format(date), " 审批人:", levelUserName);
}
return next.doAuth(uId, orderId, authDate);
}
}
/**
* 二级负责人
*/
public class Level2AuthLink extends AuthLink {
private Date beginDate = f.parse("2020-11-11 00:00:00");
private Date endDate = f.parse("2020-11-31 23:59:59");
public Level2AuthLink(String levelUserId, String levelUserName) throws ParseException {
super(levelUserId, levelUserName);
}
public AuthInfo doAuth(String uId, String orderId, Date authDate) {
Date date = AuthService.queryAuthInfo(levelUserId, orderId);
if (null == date) {
return new AuthInfo("0001", "单号:", orderId, " 状态:待二级审批负责人 ", levelUserName);
}
AuthLink next = super.getNext();
if (null == next) {
return new AuthInfo("0000", "单号:", orderId, " 状态:二级审批完成", " 时间:", f.format(date), " 审批人:", levelUserName);
}
if (authDate.before(beginDate) || authDate.after(endDate) ) {
return new AuthInfo("0000", "单号:", orderId, " 状态:二级审批完成", " 时间:", f.format(date), " 审批人:", levelUserName);
}
return next.doAuth(uId, orderId, authDate);
}
}
/**
* 三级负责人
*/
public class Level3AuthLink extends AuthLink {
public Level3AuthLink(String levelUserId, String levelUserName) {
super(levelUserId, levelUserName);
}
public AuthInfo doAuth(String uId, String orderId, Date authDate) {
Date date = AuthService.queryAuthInfo(levelUserId, orderId);
if (null == date) {
return new AuthInfo("0001", "单号:", orderId, " 状态:待三级审批负责人 ", levelUserName);
}
AuthLink next = super.getNext();
if (null == next) {
return new AuthInfo("0000", "单号:", orderId, " 状态:三级审批完成", " 时间:", f.format(date), " 审批人:", levelUserName);
}
return next.doAuth(uId, orderId, authDate);
}
}
测试
public class Client {
private Logger logger = LoggerFactory.getLogger(ApiTest.class);
@Test
public void test_AuthLink() throws ParseException {
AuthLink authLink = new Level3AuthLink("1000013", "王工")
.appendNext(new Level2AuthLink("1000012", "张经理")
.appendNext(new Level1AuthLink("1000011", "段总")));
SimpleDateFormat f = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date currentDate = f.parse("2020-11-18 23:49:46");
logger.info("测试结果:{}", JSON.toJSONString(authLink.doAuth("研发牛马", "1000998004813441", currentDate)));
// 模拟三级负责人审批
AuthService.auth("1000013", "1000998004813441");
logger.info("测试结果:{}", "模拟三级负责人审批,王工");
logger.info("测试结果:{}", JSON.toJSONString(authLink.doAuth("研发牛马", "1000998004813441", currentDate)));
// 模拟二级负责人审批
AuthService.auth("1000012", "1000998004813441");
logger.info("测试结果:{}", "模拟二级负责人审批,张经理");
logger.info("测试结果:{}", JSON.toJSONString(authLink.doAuth("研发牛马", "1000998004813441", currentDate)));
// 模拟一级负责人审批
AuthService.auth("1000011", "1000998004813441");
logger.info("测试结果:{}", "模拟一级负责人审批,段总");
logger.info("测试结果:{}", JSON.toJSONString(authLink.doAuth("研发牛马", "1000998004813441", currentDate)));
}
}
从上面的代码结果看,我们的责任链已经生效,按照责任链的结构一层一层审批.当工作流程发生变化,可以动态地改变链内的成员或者修改它们的次序,也可动态地新增或者删除责任。并且每个类只需要处理自己该处理的工作,不能处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。
1) 职责链模式的优点:
降低了对象之间的耦合度
该模式降低了请求发送者和接收者的耦合度。
增强了系统的可扩展性
可以根据需要增加新的请求处理类,满足开闭原则。
增强了给对象指派职责的灵活性
当工作流程发生变化,可以动态地改变链内的成员或者修改它们的次序,也可动态地新增或者删除责任。
责任链简化了对象之间的连接
一个对象只需保持一个指向其后继者的引用,不需保持其他所有处理者的引用,这避免了使用众多的 if 或者 if···else 语句。
责任分担
每个类只需要处理自己该处理的工作,不能处理的传递给下一个对象完成,明确各类的责任范围,符合类的单一职责原则。
2) 职责链模式的缺点:
3) 使用场景分析
责任链模式常见的使用场景有以下几种情况。
自然界很多事物都有多种状态,而且不同状态下会具有不同的行为,这些状态在特定条件下还会发生相互转换,比如水
在软件系统中,有些对象也像水一样具有多种状态,这些状态在某些情况下能够相互转换,而且对象在不同状态下也将具有不同的行为.
状态模式(state pattern)的定义: 允许一个对象在其内部状态改变时改变它的行为. 对象看起来似乎修改了它的类.
状态模式就是用于解决系统中复杂对象的状态转换以及不同状态下行为的封装问题. 状态模式将一个对象的状态从该对象中分离出来,封装到专门的状态类中(用类来表示状态) ,使得对象状态可以灵活变化.
状态模式结构图:
从这个 UML 图中,我们能看出状态模式包含的关键角色有三个。
上下文信息类(Context):实际上就是存储当前状态的类,对外提供更新状态的操作。在该类中维护着一个抽象状态接口State实例,这个实例定义当前状态.
抽象状态类(State):可以是一个接口或抽象类,用于定义声明状态更新的操作方法有哪些,具体实现由子类完成。
具体状态类(StateA 等):实现抽象状态类定义的方法,根据具体的场景来指定对应状态改变后的代码实现逻辑。
代码示例
/**
* 抽象状态接口
* @author spikeCong
* @date 2022/10/17
**/
public interface State {
//声明抽象方法,不同具体状态类可以有不同实现
void handle(Context context);
}
/**
* 上下文类
* @author spikeCong
* @date 2022/10/17
**/
public class Context {
private State currentState; //维持一个对状态对象的引用
public Context() {
this.currentState = null;
}
public State getCurrentState() {
return currentState;
}
public void setCurrentState(State currentState) {
this.currentState = currentState;
}
@Override
public String toString() {
return "Context{" +
"currentState=" + currentState +
'}';
}
}
public class ConcreteStateA implements State {
@Override
public void handle(Context context) {
System.out.println("进入状态模式A......");
context.setCurrentState(this);
}
@Override
public String toString() {
return "当前状态: ConcreteStateA";
}
}
public class ConcreteStateB implements State{
@Override
public void handle(Context context) {
System.out.println("进入状态模式B......");
context.setCurrentState(this);
}
@Override
public String toString() {
return "当前状态: ConcreteStateB";
}
}
public class Client {
public static void main(String[] args) {
Context context = new Context();
State state1 = new ConcreteStateA();
state1.handle(context);
System.out.println(context.getCurrentState().toString());
System.out.println("========================");
State state2 = new ConcreteStateB();
state2.handle(context);
System.out.println(context.getCurrentState().toString());
}
}
模拟交通信号灯的状态转换. 交通信号灯一般包括了红、黄、绿3种颜色状态,不同状态之间的切换逻辑为: 红灯只能切换为黄灯,黄灯可以切换为绿灯或红灯,绿灯只能切换为黄灯.
1) 不使用设计模式
/**
* 交通灯类
* 红灯(禁行) ,黄灯(警示),绿灯(通行) 三种状态.
* @author spikeCong
* @date 2022/10/17
**/
public class TrafficLight {
//初始状态红灯
private String state = "红";
//切换为绿灯(通行)状态
public void switchToGreen(){
if("绿".equals(state)){//当前是绿灯
System.out.println("当前为绿灯状态,无需切换!");
}else if("红".equals(state)){
System.out.println("红灯不能切换为绿灯!");
}else if("黄".equals(state)){
state = "绿";
System.out.println("绿灯亮起...时长: 60秒");
}
}
//切换为黄灯(警示)状态
public void switchToYellow(){
if("黄".equals(state)){//当前是黄灯
System.out.println("当前为黄灯状态,无需切换!");
}else if("红".equals(state) || "绿".equals(state)){
state = "黄";
System.out.println("黄灯亮起...时长:10秒");
}
}
//切换为黄灯(警示)状态
public void switchToRed(){
if("红".equals(state)){//当前是绿灯
System.out.println("当前为红灯状态,无需切换!");
}else if("绿".equals(state)){
System.out.println("绿灯不能切换为红灯!");
}else if("黄".equals(state)){
state = "红";
System.out.println("红灯亮起...时长: 90秒");
}
}
}
问题: 状态切换的操作全部在一个类中,如果有很多的交通灯进行联动,这个程序的逻辑就会变得非常复杂,难以维护.
2) 使用状态模式,将交通灯的切换逻辑组织起来,把跟状态有关的内容从交通灯类里抽离出来,使用类来表示不同的状态.
/**
* 交通灯类
* 红灯(禁行) ,黄灯(警示),绿灯(通行) 三种状态.
* @author spikeCong
* @date 2022/10/17
**/
public class TrafficLight {
//初始状态红灯
State state = new Red();
public void setState(State state) {
this.state = state;
}
//切换为绿灯状态
public void switchToGreen(){
state.switchToGreen(this);
}
//切换为黄灯状态
public void switchToYellow(){
state.switchToYellow(this);
}
//切换为红灯状态
public void switchToRed(){
state.switchToRed(this);
}
}
/**
* 交通灯状态接口
* @author spikeCong
* @date 2022/10/17
**/
public interface State {
void switchToGreen(TrafficLight trafficLight); //切换为绿灯
void switchToYellow(TrafficLight trafficLight); //切换为黄灯
void switchToRed(TrafficLight trafficLight); //切换为红灯
}
/**
* 红灯状态类
* @author spikeCong
* @date 2022/10/17
**/
public class Red implements State {
@Override
public void switchToGreen(TrafficLight trafficLight) {
System.out.println("红灯不能切换为绿灯!");
}
@Override
public void switchToYellow(TrafficLight trafficLight) {
System.out.println("黄灯亮起...时长:10秒!");
}
@Override
public void switchToRed(TrafficLight trafficLight) {
System.out.println("已是红灯状态无须再切换!");
}
}
/**
* 绿灯状态类
* @author spikeCong
* @date 2022/10/17
**/
public class Green implements State {
@Override
public void switchToGreen(TrafficLight trafficLight) {
System.out.println("已是绿灯无须切换!");
}
@Override
public void switchToYellow(TrafficLight trafficLight) {
System.out.println("黄灯亮起...时长:10秒!");
}
@Override
public void switchToRed(TrafficLight trafficLight) {
System.out.println("绿灯不能切换为红灯!");
}
}
/**
* 黄灯状态类
* @author spikeCong
* @date 2022/10/17
**/
public class Yellow implements State {
@Override
public void switchToGreen(TrafficLight trafficLight) {
System.out.println("绿灯亮起...时长:60秒!");
}
@Override
public void switchToYellow(TrafficLight trafficLight) {
System.out.println("已是黄灯无须切换!");
}
@Override
public void switchToRed(TrafficLight trafficLight) {
System.out.println("红灯亮起...时长:90秒!");
}
}
public class Client {
public static void main(String[] args) {
TrafficLight trafficLight = new TrafficLight();
trafficLight.switchToYellow();
trafficLight.switchToGreen();
trafficLight.switchToRed();
}
}
通过代码重构,将"状态" 接口化、模块化,最终将它们从臃肿的交通类中抽了出来, 消除了原来TrafficLight类中的if…else,代码看起来干净而优雅.
1) 状态模式的优点:
2) 状态模式的缺点:
3) 状态模式常见的使用场景:
迭代器模式是我们学习一个设计时很少用到的、但编码实现时却经常使用到的行为型设计模式。在绝大多数编程语言中,迭代器已经成为一个基础的类库,直接用来遍历集合对象。在平时开发中,我们更多的是直接使用它,很少会从零去实现一个迭代器。
迭代器模式(Iterator pattern)又叫游标(Cursor)模式,它的原始定义是:迭代器提供一种对容器对象中的各个元素进行访问的方法,而又不需要暴露该对象的内部细节。
在软件系统中,容器对象拥有两个职责: 一是存储数据,而是遍历数据.从依赖性上看,前者是聚合对象的基本职责.而后者是可变化的,又是可分离的.因此可以将遍历数据的行为从容器中抽取出来,封装到迭代器对象中,由迭代器来提供遍历数据的行为,这将简化聚合对象的设计,更加符合单一职责原则
迭代器模式结构图
迭代器模式主要包含以下角色:
/**
* 迭代器接口
* @author spikeCong
* @date 2022/10/18
**/
public interface Iterator<E> {
//判断集合中是否有下一个元素
boolean hasNext();
//将游标后移一位元素
void next();
//返回当前游标指定的元素
E currentItem();
}
/**
* 具体迭代器
* @author spikeCong
* @date 2022/10/18
**/
public class ConcreteIterator<E> implements Iterator<E>{
private int cursor; //游标
private ArrayList<E> arrayList; //容器
public ConcreteIterator(ArrayList<E> arrayList) {
this.cursor = 0;
this.arrayList = arrayList;
}
@Override
public boolean hasNext() {
return cursor != arrayList.size();
}
@Override
public void next() {
cursor++;
}
@Override
public E currentItem() {
if(cursor >= arrayList.size()){
throw new NoSuchElementException();
}
return arrayList.get(cursor);
}
}
public class Test01 {
public static void main(String[] args) {
ArrayList<String> names = new ArrayList<>();
names.add("lisi");
names.add("zhangsan");
names.add("wangwu");
Iterator<String> iterator = new ConcreteIterator(names);
while(iterator.hasNext()){
System.out.println(iterator.currentItem());
iterator.next();
}
/**
* 使用ArrayList集合中的iterator()方法获取迭代器
* 将创建迭代器的方法放入集合容器中,这样做的好处是对客户端封装了迭代器的实现细节.
*/
java.util.Iterator<String> iterator1 = names.iterator();
while(iterator1.hasNext()){
System.out.println(iterator1.next());
iterator.next();
}
}
}
为了帮助你更好地理解迭代器模式,下面我们还是通过一个简单的例子给大家演示一下
/**
* 抽象迭代器 IteratorIterator
* @author spikeCong
* @date 2022/10/18
**/
public interface IteratorIterator<E> {
void reset(); //重置为第一个元素
E next(); //获取下一个元素
E currentItem(); //检索当前元素
boolean hasNext(); //判断是否还有下一个元素存在
}
/**
* 抽象集合 ListList
* @author spikeCong
* @date 2022/10/18
**/
public interface ListList<E> {
//获取迭代器对象的抽象方法(面向接口编程)
IteratorIterator<E> Iterator();
}
/**
* 主题类
* @author spikeCong
* @date 2022/10/18
**/
public class Topic {
private String name;
public Topic(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
/**
* 具体迭代器
* @author spikeCong
* @date 2022/10/18
**/
public class TopicIterator implements IteratorIterator<Topic> {
//Topic数组
private Topic[] topics;
//记录存储位置
private int position;
public TopicIterator(Topic[] topics) {
this.topics = topics;
position = 0;
}
@Override
public void reset() {
position = 0;
}
@Override
public Topic next() {
return topics[position++];
}
@Override
public Topic currentItem() {
return topics[position];
}
@Override
public boolean hasNext() {
if(position >= topics.length){
return false;
}
return true;
}
}
/**
* 具体集合类
* @author spikeCong
* @date 2022/10/18
**/
public class TopicList implements ListList<Topic> {
private Topic[] topics;
public TopicList(Topic[] topics) {
this.topics = topics;
}
@Override
public IteratorIterator<Topic> Iterator() {
return new TopicIterator(topics);
}
}
public class Client {
public static void main(String[] args) {
Topic[] topics = new Topic[4];
topics[0] = new Topic("topic1");
topics[1] = new Topic("topic2");
topics[2] = new Topic("topic3");
topics[3] = new Topic("topic4");
TopicList topicList = new TopicList(topics);
IteratorIterator<Topic> iterator = topicList.Iterator();
while(iterator.hasNext()){
Topic t = iterator.next();
System.out.println(t.getName());
}
}
}
1) 迭代器的优点:
2) 迭代器的缺点:
3) 使用场景
减少程序中重复的遍历代码
对于放入一个集合容器中的多个对象来说,访问必然涉及遍历算法。如果我们不将遍历算法封装到容器里(比如,List、Set、Map 等),那么就需要使用容器的人自行去实现遍历算法,这样容易造成很多重复的循环和条件判断语句出现,不利于代码的复用和扩展,同时还会暴露不同容器的内部结构。而使用迭代器模式是将遍历算法作为容器对象自身的一种“属性方法”来使用,能够有效地避免写很多重复的代码,同时又不会暴露内部结构。
当需要为遍历不同的集合结构提供一个统一的接口时或者当访问一个集合对象的内容而无须暴露其内部细节的表示时。
迭代器模式把对不同集合类的访问逻辑抽象出来,这样在不用暴露集合内部结构的情况下,可以隐藏不同集合遍历需要使用的算法,同时还能够对外提供更为简便的访问算法接口。
访问者模式在实际开发中使用的非常少,因为它比较难以实现并且应用该模式肯能会导致代码的可读性变差,可维护性变差,在没有特别必要的情况下,不建议使用访问者模式.
访问者模式(Visitor Pattern) 的原始定义是:允许在运行时将一个或多个操作应用于一组对象,将操作与对象结构分离。
这个定义会比较抽象,但是我们依然能看出两个关键点:
一个是: 运行时使用一组对象的一个或多个操作,比如,对不同类型的文件(.pdf、.xml、.properties)进行扫描;
另一个是: 分离对象的操作和对象本身的结构,比如,扫描多个文件夹下的多个文件,对于文件来说,扫描是额外的业务操作,如果在每个文件对象上都加一个扫描操作,太过于冗余,而扫描操作具有统一性,非常适合访问者模式。
访问者模式主要解决的是数据与算法的耦合问题, 尤其是在数据结构比较稳定,而算法多变的情况下.为了不污染数据本身,访问者会将多种算法独立归档,并在访问数据时根据数据类型自动切换到对应的算法,实现数据的自动响应机制,并确保算法的自由扩展.
访问者模式包含以下主要角色:
accept
),其意义是指,每一个元素都要可以被访问者访问。我们以超市购物为例,假设超市中的三类商品: 水果,糖果,酒水进行售卖. 我们可以忽略每种商品的计价方法,因为最终结账时由收银员统一集中处理,在商品类中添加计价方法是不合理的设计.我们先来定义糖果类和酒类、水果类.
/**
* 抽象商品父类
* @author spikeCong
* @date 2022/10/18
**/
public abstract class Product {
private String name; //商品名
private LocalDate producedDate; // 生产日期
private double price; //单品价格
public Product(String name, LocalDate producedDate, double price) {
this.name = name;
this.producedDate = producedDate;
this.price = price;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public LocalDate getProducedDate() {
return producedDate;
}
public void setProducedDate(LocalDate producedDate) {
this.producedDate = producedDate;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
}
/**
* 糖果类
* @author spikeCong
* @date 2022/10/18
**/
public class Candy extends Product{
public Candy(String name, LocalDate producedDate, double price) {
super(name, producedDate, price);
}
}
/**
* 酒水类
* @author spikeCong
* @date 2022/10/18
**/
public class Wine extends Product{
public Wine(String name, LocalDate producedDate, double price) {
super(name, producedDate, price);
}
}
/**
* 水果类
* @author spikeCong
* @date 2022/10/18
**/
public class Fruit extends Product{
//重量
private float weight;
public Fruit(String name, LocalDate producedDate, double price, float weight) {
super(name, producedDate, price);
this.weight = weight;
}
public float getWeight() {
return weight;
}
public void setWeight(float weight) {
this.weight = weight;
}
}
访问者接口
/**
* 访问者接口-根据入参不同调用对应的重载方法
* @author spikeCong
* @date 2022/10/18
**/
public interface Visitor {
public void visit(Candy candy); //糖果重载方法
public void visit(Wine wine); //酒类重载方法
public void visit(Fruit fruit); //水果重载方法
}
具体访问者
/**
* 折扣计价访问者类
* @author spikeCong
* @date 2022/10/18
**/
public class DiscountVisitor implements Visitor {
private LocalDate billDate;
public DiscountVisitor(LocalDate billDate) {
this.billDate = billDate;
System.out.println("结算日期: " + billDate);
}
@Override
public void visit(Candy candy) {
System.out.println("糖果: " + candy.getName());
//获取产品生产天数
long days = billDate.toEpochDay() - candy.getProducedDate().toEpochDay();
if(days > 180){
System.out.println("超过半年的糖果,请勿食用!");
}else{
double rate = 0.9;
double discountPrice = candy.getPrice() * rate;
System.out.println("糖果打折后的价格"+NumberFormat.getCurrencyInstance().format(discountPrice));
}
}
@Override
public void visit(Wine wine) {
System.out.println("酒类: " + wine.getName()+",无折扣价格!");
System.out.println("原价: "+NumberFormat.getCurrencyInstance().format(wine.getPrice()));
}
@Override
public void visit(Fruit fruit) {
System.out.println("水果: " + fruit.getName());
//获取产品生产天数
long days = billDate.toEpochDay() - fruit.getProducedDate().toEpochDay();
double rate = 0;
if(days > 7){
System.out.println("超过七天的水果,请勿食用!");
}else if(days > 3){
rate = 0.5;
}else{
rate = 1;
}
double discountPrice = fruit.getPrice() * fruit.getWeight() * rate;
System.out.println("水果价格: "+NumberFormat.getCurrencyInstance().format(discountPrice));
}
public static void main(String[] args) {
LocalDate billDate = LocalDate.now();
Candy candy = new Candy("徐福记",LocalDate.of(2022,10,1),10.0);
System.out.println("糖果: " + candy.getName());
double rate = 0.0;
long days = billDate.toEpochDay() - candy.getProducedDate().toEpochDay();
System.out.println(days);
if(days > 180){
System.out.println("超过半年的糖果,请勿食用!");
}else{
rate = 0.9;
double discountPrice = candy.getPrice() * rate;
System.out.println("打折后的价格"+NumberFormat.getCurrencyInstance().format(discountPrice));
}
}
}
客户端
public class Client {
public static void main(String[] args) {
//德芙巧克力,生产日期2002-5-1 ,原价 10元
Candy candy = new Candy("德芙巧克力",LocalDate.of(2022,5,1),10.0);
Visitor visitor = new DiscountVisitor(LocalDate.of(2022,10,11));
visitor.visit(candy);
}
}
上面的代码虽然可以完成当前的需求,但是设想一下这样一个场景: 由于访问者的重载方法只能对当个的具体商品进行计价,如果顾客选择了多件商品来结账时,就可能会引起重载方法的派发问题(到底该由谁来计算的问题).
首先我们定义一个接待访问者的类 Acceptable,其中定义了一个accept(Visitor visitor)方法, 只要是visitor的子类都可以接收.
/**
* 接待者接口(抽象元素角色)
* @author spikeCong
* @date 2022/10/18
**/
public interface Acceptable {
//接收所有的Visitor访问者的子类实现类
public void accept(Visitor visitor);
}
/**
* 糖果类
* @author spikeCong
* @date 2022/10/18
**/
public class Candy extends Product implements Acceptable{
public Candy(String name, LocalDate producedDate, double price) {
super(name, producedDate, price);
}
@Override
public void accept(Visitor visitor) {
//accept实现方法中调用访问者并将自己 "this" 传回。this是一个明确的身份,不存在任何泛型
visitor.visit(this);
}
}
//酒水与水果类同样实现Acceptable接口,重写accept方法
测试
public class Client {
public static void main(String[] args) {
// //德芙巧克力,生产日期2002-5-1 ,原价 10元
Candy candy = new Candy("德芙巧克力",LocalDate.of(2022,5,1),10.0);
Visitor visitor = new DiscountVisitor(LocalDate.of(2022,10,11));
visitor.visit(candy);
//模拟添加多个商品的操作
List<Acceptable> products = Arrays.asList(
new Candy("金丝猴奶糖",LocalDate.of(2022,6,10),10.00),
new Wine("衡水老白干",LocalDate.of(2020,6,10),100.00),
new Fruit("草莓",LocalDate.of(2022,10,12),50.00,1)
);
Visitor visitor = new DiscountVisitor(LocalDate.of(2022,10,17));
for (Acceptable product : products) {
product.accept(visitor);
}
}
}
代码编写到此出,就可以应对计价方式或者业务逻辑的变化了,访问者模式成功地将数据资源(需实现接待者接口)与数据算法 (需实现访问者接口)分离开来。重载方法的使用让多样化的算法自成体系,多态化的访问者接口保证了系统算法的可扩展性,数据则保持相对固定,最终形成⼀个算法类对应⼀套数据。
1) 访问者模式优点:
扩展性好
在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。
复用性好
通过访问者来定义整个对象结构通用的功能,从而提高复用程度。
分离无关行为
通过访问者来分离无关的行为,把相关的行为封装在一起,构成一个访问者,这样每一个访问者的功能都比较单一。
2) 访问者模式缺点:
对象结构变化很困难
在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”。
违反了依赖倒置原则
访问者模式依赖了具体类,而没有依赖抽象类。
3) 使用场景
当对象的数据结构相对稳定,而操作却经常变化的时候。 比如,上面例子中路由器本身的内部构造(也就是数据结构)不会怎么变化,但是在不同操作系统下的操作可能会经常变化,比如,发送数据、接收数据等。
需要将数据结构与不常用的操作进行分离的时候。 比如,扫描文件内容这个动作通常不是文件常用的操作,但是对于文件夹和文件来说,和数据结构本身没有太大关系(树形结构的遍历操作),扫描是一个额外的动作,如果给每个文件都添加一个扫描操作会太过于重复,这时采用访问者模式是非常合适的,能够很好分离文件自身的遍历操作和外部的扫描操作。
需要在运行时动态决定使用哪些对象和方法的时候。 比如,对于监控系统来说,很多时候需要监控运行时的程序状态,但大多数时候又无法预知对象编译时的状态和参数,这时使用访问者模式就可以动态增加监控行为。
备忘录模式提供了一种对象状态的撤销实现机制,当系统中某一个对象需要恢复到某一历史状态时可以使用备忘录模式进行设计.
很多软件都提供了撤销(Undo)操作,如 Word、记事本、Photoshop、IDEA等软件在编辑时按 Ctrl+Z 组合键时能撤销当前操作,使文档恢复到之前的状态;还有在 浏览器 中的后退键、数据库事务管理中的回滚操作、玩游戏时的中间结果存档功能、数据库与操作系统的备份操作、棋类游戏中的悔棋功能等都属于这类。
备忘录模式(memento pattern)定义: 在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态.
备忘录模式的主要角色如下:
下面我们再来看看 UML 对应的代码实现。首先,我们创建原始对象 Originator,对象中有四个属性,分别是 state 用于显示当前对象状态,id、name、phone 用来模拟业务属性,并添加 get、set 方法、create() 方法用于创建备份对象,restore(memento) 用于恢复对象状态。
/**
* 发起人类
* @author spikeCong
* @date 2022/10/19
**/
public class Originator {
private String state = "原始对象";
private String id;
private String name;
private String phone;
public Originator() {
}
//创建备忘录对象
public Memento create(){
return new Memento(id,name,phone);
}
//恢复对象状态
public void restore(Memento m){
this.state = m.getState();
this.id = m.getId();
this.name = m.getName();
this.phone = m.getPhone();
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
@Override
public String toString() {
return "Originator{" +
"state='" + state + '\'' +
", id='" + id + '\'' +
", name='" + name + '\'' +
", phone='" + phone + '\'' +
'}';
}
}
/**
* 备忘录对象
* 访问权限为: 默认,也就是同包下可见(保证只有发起者类可以访问备忘录类)
* @author spikeCong
* @date 2022/10/19
**/
class Memento {
private String state = "从备份对象恢复为原始对象";
private String id;
private String name;
private String phone;
public Memento() {
}
public Memento(String id, String name, String phone) {
this.id = id;
this.name = name;
this.phone = phone;
}
//get、set、toString......
}
/**
* 负责人类-保存备忘录对象
* @author spikeCong
* @date 2022/10/19
**/
public class Caretaker {
private Memento memento;
public Memento getMemento() {
return memento;
}
public void setMemento(Memento memento) {
this.memento = memento;
}
}
public class Client {
public static void main(String[] args) {
//创建发起人对象
Originator originator = new Originator();
originator.setId("1");
originator.setName("spike");
originator.setPhone("13512341234");
System.out.println("=============" + originator);
//创建负责人对象,并保存备忘录对象
Caretaker caretaker = new Caretaker();
caretaker.setMemento(originator.create());
//修改
originator.setName("update");
System.out.println("=============" + originator);
//从负责人对象中获取备忘录对象,实现撤销
originator.restore(caretaker.getMemento());
System.out.println("=============" + originator);
}
}
设计一个收集水果和获取金钱数的掷骰子游戏,游戏规则如下
/**
* Memento 表示状态
* @author spikeCong
* @date 2022/10/19
**/
public class Memento {
int money; //所持金钱
ArrayList fruits; //获得的水果
//构造函数
Memento(int money) {
this.money = money;
this.fruits = new ArrayList();
}
//获取当前玩家所有的金钱
int getMoney() {
return money;
}
//获取当前玩家所有的水果
List getFruits() {
return (List)fruits.clone();
}
//添加水果
void addFruit(String fruit){
fruits.add(fruit);
}
}
package com.mashibing.memento.example02;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/**
* @author spikeCong
* @date 2022/10/19
**/
public class Player {
private int money; //所持金钱
private List<String> fruits = new ArrayList(); //获得的水果
private Random random = new Random(); //随机数对象
private static String[] fruitsName={ //表示水果种类的数组
"苹果","葡萄","香蕉","橘子"
};
//构造方法
public Player(int money) {
this.money = money;
}
//获取当前所持有的金钱
public int getMoney() {
return money;
}
//获取一个水果
public String getFruit() {
String prefix = "";
if (random.nextBoolean()) {
prefix = "好吃的";
}
//从数组中获取水果
String f = fruitsName[random.nextInt(fruitsName.length)];
return prefix + f;
}
//掷骰子游戏
public void yacht(){
int dice = random.nextInt(6) + 1; //掷骰子
if(dice == 1){
money += 100;
System.out.println("所持有的金钱增加了..");
}else if(dice == 2){
money /= 2;
System.out.println("所持有的金钱减半..");
}else if(dice == 6){ //获取水果
String fruit = getFruit();
System.out.println("获得了水果: " + fruit);
fruits.add(fruit);
}else{
//骰子结果为3、4、5
System.out.println("无效数字,继续投掷");
}
}
//拍摄快照
public Memento createMemento(){
Memento memento = new Memento(money);
for (String fruit : fruits) {
if(fruit.startsWith("好吃的")){
memento.addFruit(fruit);
}
}
return memento;
}
//撤销方法
public void restore(Memento memento){
this.money = memento.money;
this.fruits = memento.getFruits();
}
@Override
public String toString() {
return "Player{" +
"money=" + money +
", fruits=" + fruits +
'}';
}
}
public class MainApp {
public static void main(String[] args) throws InterruptedException {
Player player = new Player(100); //最初所持的金钱数
Memento memento = player.createMemento(); //保存最初状态
for (int i = 0; i < 100; i++) {
//显示扔骰子的次数
System.out.println("=====" + i);
//显示当前状态
System.out.println("当前状态: " + player);
//开启游戏
player.yacht();
System.out.println("所持有的金钱为: " + player.getMoney() + " 元");
//决定如何操作Memento
if(player.getMoney() > memento.getMoney()){
System.out.println("赚到金币,保存当前状态,继续游戏!");
memento = player.createMemento();
}else if(player.getMoney() < memento.getMoney() / 2){
System.out.println("所持金币不多了,将游戏恢复到初始状态!");
player.restore(memento);
}
Thread.sleep(1000);
System.out.println("");
}
}
}
1 ) 备忘录模式的优点
2 ) 备忘录模式的缺点
3) 备忘录模式使用场景
命令模式(command pattern)的定义: 命令模式将请求(命令)封装为一个对象,这样可以使用不同的请求参数化其他对象(将不 同请求依赖注入到其他对象),并且能够支持请求(命令)的排队执行、记录日志、撤销等 (附加控制)功能。
命令模式的核心是将指令信息封装成一个对象,并将此对象作为参数发送给接收方去执行,达到使命令的请求与执行方解耦,双方只通过传递各种命令对象来完成任务.
在实际的开发中,如果你用到的编程语言并不支持用函数作为参数来传递,那么就可以借助命令模式将函数封装为对象来使用。
我们知道,C 语 言支持函数指针,我们可以把函数当作变量传递来传递去。但是,在大部分编程语言中,函 数没法儿作为参数传递给其他函数,也没法儿赋值给变量。借助命令模式,我们可以将函数 封装成对象。具体来说就是,设计一个包含这个函数的类,实例化一个对象传来传去,这样 就可以实现把函数像对象一样使用。
命令模式包含以下主要角色:
模拟酒店后厨的出餐流程,来对命令模式进行一个演示,命令模式角色的角色与案例中角色的对应关系如下:
/**
* 订单类
* @author spikeCong
* @date 2022/10/19
**/
public class Order {
private int diningTable; //餐桌号码
//存储菜名与份数
private Map<String,Integer> foodMenu = new HashMap<>();
public int getDiningTable() {
return diningTable;
}
public void setDiningTable(int diningTable) {
this.diningTable = diningTable;
}
public Map<String, Integer> getFoodMenu() {
return foodMenu;
}
public void setFoodDic(Map<String, Integer> foodMenu) {
this.foodMenu = foodMenu;
}
}
/**
* 厨师类 -> Receiver角色
* @author spikeCong
* @date 2022/10/19
**/
public class Chef {
public void makeFood(int num,String foodName){
System.out.println(num + "份," + foodName);
}
}
/**
* 抽象命令接口
* @author spikeCong
* @date 2022/10/19
**/
public interface Command {
void execute(); //只需要定义一个统一的执行方法
}
/**
* 具体命令
* @author spikeCong
* @date 2022/10/19
**/
public class OrderCommand implements Command {
//持有接收者对象
private Chef receiver;
private Order order;
public OrderCommand(Chef receiver, Order order) {
this.receiver = receiver;
this.order = order;
}
@Override
public void execute() {
System.out.println(order.getDiningTable() + "桌的订单: ");
Set<String> keys = order.getFoodMenu().keySet();
for (String key : keys) {
receiver.makeFood(order.getFoodMenu().get(key),key);
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(order.getDiningTable() + "桌的菜已上齐.");
}
}
/**
* 服务员-> Invoker调用者
* @author spikeCong
* @date 2022/10/19
**/
public class Waiter {
//可以持有很多的命令对象
private ArrayList<Command> commands;
public Waiter() {
commands = new ArrayList();
}
public Waiter(ArrayList<Command> commands) {
this.commands = commands;
}
public void setCommands(Command command) {
commands.add(command);
}
//发出命令 ,指挥厨师工作
public void orderUp(){
System.out.println("服务员: 叮咚,有新的订单,请厨师开始制作......");
for (Command cmd : commands) {
if(cmd != null){
cmd.execute();
}
}
}
}
public class Client {
public static void main(String[] args) {
Order order1 = new Order();
order1.setDiningTable(1);
order1.getFoodMenu().put("鲍鱼炒饭",1);
order1.getFoodMenu().put("茅台迎宾",1);
Order order2 = new Order();
order2.setDiningTable(3);
order2.getFoodMenu().put("海参炒面",1);
order2.getFoodMenu().put("五粮液",1);
//创建接收者
Chef receiver = new Chef();
//将订单和接收者封装成命令对象
OrderCommand cmd1 = new OrderCommand(receiver,order1);
OrderCommand cmd2 = new OrderCommand(receiver,order2);
//创建调用者
Waiter invoke = new Waiter();
invoke.setCommands(cmd1);
invoke.setCommands(cmd2);
//将订单发送到后厨
invoke.orderUp();
}
}
1) 命令模式优点:
2) 命令模式缺点:
3) 使用场景
解释器模式使用频率不算高,通常用来描述如何构建一个简单“语言”的语法解释器。它只在一些非常特定的领域被用到,比如编译器、规则引擎、正则表达式、SQL 解析等。不过,了解它的实现原理同样很重要,能帮助你思考如何通过更简洁的规则来表示复杂的逻辑。
解释器模式(Interpreter pattern)的原始定义是:用于定义语言的语法规则表示,并提供解释器来处理句子中的语法。
我们通过一个例子给大家解释一下解释器模式
//用于两个整数相加的方法
public static int add(int a , int b){
return a + b;
}
//用于三个整数相加的方法
public static int add(int a , int b,int c){
return a + b + c;
}
public static int add(Integer ... arr){
int sum = 0;
for(Integer num : arr){
sum += num;
}
return sum;
}
+ -
上面的形式比较单一、有限,如果形式变化非常多,这就不符合要求,因为加法和减法运算,两个运算符与数值可以有无限种组合方式。比如: 5-3+2-1, 10-5+20…
文法规则和抽象语法树
解释器模式描述了如何为简单的语言定义一个文法,如何在该语言中表示一个句子,以及如何解释这些句子.
在上面提到的加法/减法解释器中,每一个输入表达式(比如:2+3+4-5) 都包含了3个语言单位,可以使用下面的文法规则定义:
文法是用于描述语言的语法结构的形式规则。
expression ::= value | plus | minus
plus ::= expression ‘+’ expression
minus ::= expression ‘-’ expression
value ::= integer
注意: 这里的符号“::=”表示“定义为”的意思,竖线 | 表示或,左右的其中一个,引号内为字符本身,引号外为语法。
上面规则描述为 :
表达式可以是一个值,也可以是plus或者minus运算,而plus和minus又是由表达式结合运算符构成,值的类型为整型数。
抽象语法树:
在解释器模式中还可以通过一种称为抽象语法树的图形方式来直观的表示语言的构成,每一棵抽象语法树对应一个语言实例,例如加法/减法表达式语言中的语句 " 1+ 2 + 3 - 4 + 1" 可以通过下面的抽象语法树表示
解释器模式包含以下主要角色。
为了更好的给大家解释一下解释器模式, 我们来定义了一个进行加减乘除计算的“语言”,语法规则如下:
我们举个例子来解释一下上面的语法规则:
“ 9 5 7 3 - + * ”
这样一个表达式,我们按照上面的语法规则来处理,取出数字 “9、5”
和 “-”
运算符,计算得到 4,于是表达式就变成了“ 4 7 3 + * ”
。然后,我们再取出“4 7”
和“ + ”运算符,计算得到 11,表达式就变成了“ 11 3 * ”。最后,我们取出“ 11 3”和“ * ”运算符,最终得到的结果就是 33。代码示例:
/**
* 表达式解释器类
* @author spikeCong
* @date 2022/10/20
**/
public class ExpressionInterpreter {
//Deque双向队列,可以从队列的两端增加或者删除元素
private Deque<Long> numbers = new LinkedList<>();
//接收表达式进行解析
public long interpret(String expression){
String[] elements = expression.split(" ");
int length = elements.length;
//获取表达式中的数字
for (int i = 0; i < (length+1)/2; ++i) {
//在 Deque的尾部添加元素
numbers.addLast(Long.parseLong(elements[i]));
}
//获取表达式中的符号
for (int i = (length+1)/2; i < length; ++i) {
String operator = elements[i];
//符号必须是 + - * / 否则抛出异常
boolean isValid = "+".equals(operator) || "-".equals(operator)
|| "*".equals(operator) || "/".equals(operator);
if (!isValid) {
throw new RuntimeException("Expression is invalid: " + expression);
}
//pollFirst()方法, 移除Deque中的第一个元素,并返回被移除的值
long number1 = numbers.pollFirst(); //数字
long number2 = numbers.pollFirst();
long result = 0; //运算结果
//对number1和number2进行运算
if (operator.equals("+")) {
result = number1 + number2;
} else if (operator.equals("-")) {
result = number1 - number2;
} else if (operator.equals("*")) {
result = number1 * number2;
} else if (operator.equals("/")) {
result = number1 / number2;
}
//将运算结果添加到集合头部
numbers.addFirst(result);
}
//运算完成numbers中应该保存着运算结果,否则是无效表达式
if (numbers.size() != 1) {
throw new RuntimeException("Expression is invalid: " + expression);
}
//移除Deque的第一个元素,并返回
return numbers.pop();
}
}
代码重构
上面代码的所有的解析逻辑都耦合在一个函数中,这样显然是不合适的。这 个时候,我们就要考虑拆分代码,将解析逻辑拆分到独立的小类中, 前面定义的语法规则有两类表达式,一类是数字,一类是运算符,运算符又包括加减乘除。 利用解释器模式,我们把解析的工作拆分到以下五个类:plu,sub,mul,div
/**
* 表达式接口
* @author spikeCong
* @date 2022/10/20
**/
public interface Expression {
long interpret();
}
/**
* 数字表达式
* @author spikeCong
* @date 2022/10/20
**/
public class NumExpression implements Expression {
private long number;
public NumExpression(long number) {
this.number = number;
}
public NumExpression(String number) {
this.number = Long.parseLong(number);
}
@Override
public long interpret() {
return this.number;
}
}
/**
* 加法运算
* @author spikeCong
* @date 2022/10/20
**/
public class PluExpression implements Expression{
private Expression exp1;
private Expression exp2;
public PluExpression(Expression exp1, Expression exp2) {
this.exp1 = exp1;
this.exp2 = exp2;
}
@Override
public long interpret() {
return exp1.interpret() + exp2.interpret();
}
}
/**
* 减法运算
* @author spikeCong
* @date 2022/10/20
**/
public class SubExpression implements Expression {
private Expression exp1;
private Expression exp2;
public SubExpression(Expression exp1, Expression exp2) {
this.exp1 = exp1;
this.exp2 = exp2;
}
@Override
public long interpret() {
return exp1.interpret() - exp2.interpret();
}
}
/**
* 乘法运算
* @author spikeCong
* @date 2022/10/20
**/
public class MulExpression implements Expression {
private Expression exp1;
private Expression exp2;
public MulExpression(Expression exp1, Expression exp2) {
this.exp1 = exp1;
this.exp2 = exp2;
}
@Override
public long interpret() {
return exp1.interpret() * exp2.interpret();
}
}
/**
* 除法
* @author spikeCong
* @date 2022/10/20
**/
public class DivExpression implements Expression {
private Expression exp1;
private Expression exp2;
public DivExpression(Expression exp1, Expression exp2) {
this.exp1 = exp1;
this.exp2 = exp2;
}
@Override
public long interpret() {
return exp1.interpret() / exp2.interpret();
}
}
//测试
public class Test01 {
public static void main(String[] args) {
ExpressionInterpreter e = new ExpressionInterpreter();
long result = e.interpret("6 2 3 2 4 / - + *");
System.out.println(result);
}
}
1) 解释器优点
易于改变和扩展文法
因为在解释器模式中使用类来表示语言的文法规则的,因此就可以通过继承等机制改变或者扩展文法.每一个文法规则都可以表示为一个类,因此我们可以快速的实现一个迷你的语言
实现文法比较容易
在抽象语法树中每一个表达式节点类的实现方式都是相似的,这些类的代码编写都不会特别复杂
增加新的解释表达式比较方便
如果用户需要增加新的解释表达式,只需要对应增加一个新的表达式类就可以了.原有的表达式类不需要修改,符合开闭原则
2) 解释器缺点
对于复杂文法难以维护
在解释器中一条规则至少要定义一个类,因此一个语言中如果有太多的文法规则,就会使类的个数急剧增加,当值系统的维护难以管理.
执行效率低
在解释器模式中大量的使用了循环和递归调用,所有复杂的句子执行起来,整个过程也是非常的繁琐
3) 使用场景
提到中介模式,有一个比较经典的例子就是航空管制。 为了让飞机在飞行的时候互不干扰,每架飞机都需要知道其他飞机每时每刻的位置,这就需要时刻跟其他飞机通信。飞机通信形成的通信网络就会无比复杂。这个时候,我们通过引 入“塔台”这样一个中介,让每架飞机只跟塔台来通信,发送自己的位置给塔台,由塔台来 负责每架飞机的航线调度。这样就大大简化了通信网络。
中介模式(mediator pattern)的定义: 定义一个单独的(中介)对象,来封装一组对象之间的交互,将这组对象之间的交互委派给予中介对象交互,来避免对象之间的交互.
中介者对象就是用于处理对象与对象之间的直接交互,封装了多个对象之间的交互细节。中介模式的设计跟中间层很像,通过引入中介这个中间层,将一组对象之间的交互关系从多对多的网状关系转换为一对多的星状关系.原来一个对象要跟N个对象交互,现在只需要跟一个中介对象交互,从而最小化对象之间的交互关系,降低代码的复杂度,提高代码的可读性和可维护性.
中介者模式包含以下主要角色:
抽象中介者(Mediator)角色:它是中介者的接口,提供了同事对象注册与转发同事对象信息的抽象方法。
具体中介者(ConcreteMediator)角色:实现中介者接口,定义一个 List 来管理同事对象,协调各个同事角色之间的交互关系,因此它依赖于同事角色。
抽象同事类(Colleague)角色:定义同事类的接口,保存中介者对象,提供同事对象交互的抽象方法,实现所有相互影响的同事类的公共功能。
具体同事类(Concrete Colleague)角色:是抽象同事类的实现者,当需要与其他同事对象交互时,由中介者对象负责后续的交互。
代码示例
/**
* 抽象中介者
* @author spikeCong
* @date 2022/10/20
**/
public interface Mediator {
void apply(String key);
}
/**
* 具体中介者
* @author spikeCong
* @date 2022/10/20
**/
public class MediatorImpl implements Mediator {
@Override
public void apply(String key) {
System.out.println("最终中介者执行操作,key为: " + key);
}
}
/**
* 抽象同事类
* @author spikeCong
* @date 2022/10/20
**/
public abstract class Colleague {
private Mediator mediator;
public Colleague(Mediator mediator) {
this.mediator = mediator;
}
public Mediator getMediator() {
return mediator;
}
public abstract void exec(String key);
}
/**
* 具体同事类
* @author spikeCong
* @date 2022/10/20
**/
public class ConcreteColleagueA extends Colleague {
public ConcreteColleagueA(Mediator mediator) {
super(mediator);
}
@Override
public void exec(String key) {
System.out.println("====在组件A中,通过中介者执行!");
getMediator().apply(key);
}
}
public class ConcreteColleagueB extends Colleague {
public ConcreteColleagueB(Mediator mediator) {
super(mediator);
}
@Override
public void exec(String key) {
System.out.println("====在组件B中,通过中介者执行!");
getMediator().apply(key);
}
}
public class Client {
public static void main(String[] args) {
//创建中介者
MediatorImpl mediator = new MediatorImpl();
//创建同事对象
Colleague c1 = new ConcreteColleagueA(mediator);
c1.exec("key-A");
Colleague c2 = new ConcreteColleagueB(mediator);
c2.exec("key-B");
}
}
====在组件A中,通过中介者执行!
最终中介者执行操作,key为: key-A
====在组件B中,通过中介者执行!
最终中介者执行操作,key为: key-B
【例】租房
现在租房基本都是通过房屋中介,房主将房屋托管给房屋中介,而租房者从房屋中介获取房屋信息。房屋中介充当租房者与房屋所有者之间的中介者。
/**
* 抽象中介者
* @author spikeCong
* @date 2022/10/20
**/
public abstract class Mediator {
//申明一个联络方法
public abstract void contact(String message,Person person);
}
/**
* 抽象同事类
* @author spikeCong
* @date 2022/10/20
**/
public abstract class Person {
protected String name;
protected Mediator mediator;
public Person(String name, Mediator mediator) {
this.name = name;
this.mediator = mediator;
}
}
/**
* 中介机构
* @author spikeCong
* @date 2022/10/20
**/
public class MediatorStructure extends Mediator {
//中介要知晓房主和租房者的信息
private HouseOwner houseOwner;
private Tenant tenant;
public HouseOwner getHouseOwner() {
return houseOwner;
}
public void setHouseOwner(HouseOwner houseOwner) {
this.houseOwner = houseOwner;
}
public Tenant getTenant() {
return tenant;
}
public void setTenant(Tenant tenant) {
this.tenant = tenant;
}
@Override
public void contact(String message, Person person) {
if(person == houseOwner){ //如果是房主,则租房者获得信息
tenant.getMessage(message);
}else { //如果是租房者则获取房主信息
houseOwner.getMessage(message);
}
}
}
/**
* 具体同事类-房屋拥有者
* @author spikeCong
* @date 2022/10/20
**/
public class HouseOwner extends Person{
public HouseOwner(String name, Mediator mediator) {
super(name, mediator);
}
//与中介者联系
public void contact(String message){
mediator.contact(message,this);
}
//获取信息
public void getMessage(String message){
System.out.println("房主" + name + "获取到的信息: " + message);
}
}
/**
* 具体同事类-承租人
* @author spikeCong
* @date 2022/10/20
**/
public class Tenant extends Person{
public Tenant(String name, Mediator mediator) {
super(name, mediator);
}
//与中介者联系
public void contact(String message){
mediator.contact(message,this);
}
//获取信息
public void getMessage(String message){
System.out.println("租房者" + name + "获取到的信息: " + message);
}
}
public class Client {
public static void main(String[] args) {
//一个房主 一个租房者 一个中介机构
MediatorStructure mediator = new MediatorStructure();
//房主和租房者只需要知道中介机构即可
HouseOwner houseOwner = new HouseOwner("路飞", mediator);
Tenant tenant = new Tenant("娜美", mediator);
//中介收集房租和租房者信息
mediator.setHouseOwner(houseOwner);
mediator.setTenant(tenant);
tenant.contact("需要一个两室一厅的房子,一家人住");
houseOwner.contact("出租一套两室一厅带电梯,月租5000");
}
}
1) 中介者模式的优点
2) 中介者模式的缺点
3) 中介者模式使用场景
Spring的设计理念
Bean组件定义在Spring的org.springframework.beans包下,解决了以下几个问题:
这个包下的所有类主要解决了三件事:
Spring Bean的创建是典型的工厂模式,它的顶级接口是BeanFactory。
BeanFactory有三个子类:ListableBeanFactory、HierarchicalBeanFactory和AutowireCapableBeanFactory。目的是为了区分Spring内部对象处理和转化的数据限制。
但是从图中可以发现最终的默认实现类是DefaultListableBeanFactory,它实现了所有的接口
Spring中的BeanFactory就是简单工厂模式的体现,根据传入一个唯一的标识来获得Bean对象
BeanFactory,以Factory结尾,表示它是一个工厂(接口), 它负责生产和管理bean的一个工厂。在Spring中,BeanFactory是工厂的顶层接口,也是IOC容器的核心接口,因此BeanFactory中定义了管理Bean的通用方法,如 getBean 和 containsBean 等.
它的职责包括:实例化、定位、配置应用程序中的对象及建立这些对象间的依赖。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jV8hYvdv-1667492653381)(C:\Users\86187\AppData\Roaming\Typora\typora-user-images\image-20221025193939157.png)]
BeanFactory只是个接口,并不是IOC容器的具体实现,所以Spring容器给出了很多种实现,如 DefaultListableBeanFactory、XmlBeanFactory、ApplicationContext等,其中XmlBeanFactory就是常用的一个,该实现将以XML方式描述组成应用的对象及对象间的依赖关系。
1) BeanFactory源码解析
public interface BeanFactory {
/**
对FactoryBean的转移定义,因为如果使用bean的名字来检索FactoryBean得到的是对象是工厂生成的对象,
如果想得到工厂本身就需要转移
*/
String FACTORY_BEAN_PREFIX = "&";
//根据Bean的名字 获取IOC容器中对应的实例
Object getBean(String var1) throws BeansException;
//根据Bean的名字和class类型得到bean实例,增加了类型安全验证机制
<T> T getBean(String var1, Class<T> var2) throws BeansException;
Object getBean(String var1, Object... var2) throws BeansException;
<T> T getBean(Class<T> var1) throws BeansException;
<T> T getBean(Class<T> var1, Object... var2) throws BeansException;
<T> ObjectProvider<T> getBeanProvider(Class<T> var1);
<T> ObjectProvider<T> getBeanProvider(ResolvableType var1);
//查看Bean容器中是否存在对应的实例,存在返回true 否则返回false
boolean containsBean(String var1);
//根据Bean的名字 判断这个bean是不是单例
boolean isSingleton(String var1) throws NoSuchBeanDefinitionException;
boolean isPrototype(String var1) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String var1, ResolvableType var2) throws NoSuchBeanDefinitionException;
boolean isTypeMatch(String var1, Class<?> var2) throws NoSuchBeanDefinitionException;
//得到bean实例的class类型
@Nullable
Class<?> getType(String var1) throws NoSuchBeanDefinitionException;
@Nullable
Class<?> getType(String var1, boolean var2) throws NoSuchBeanDefinitionException;
//得到bean的别名
String[] getAliases(String var1);
}
BeanFactory的使用场景
2) BeanFactory的使用
public class User {
private int id;
private String name;
private Friends friends;
public User() {
}
public User(Friends friends) {
this.friends = friends;
}
//get set......
}
public class Friends {
private List<String> names;
public Friends() {
}
public List<String> getNames() {
return names;
}
public void setNames(List<String> names) {
this.names = names;
}
}
配置文件
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:task="http://www.springframework.org/schema/task"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc-4.2.xsd
http://www.springframework.org/schema/task
http://www.springframework.org/schema/task/spring-task-4.2.xsd">
<bean id="User" class="com.example.factory.User">
<property name="friends" ref="UserFriends" />
bean>
<bean id="UserFriends" class="com.example.factory.Friends">
<property name="names">
<list>
<value>"LiLi"value>
<value>"LuLu"value>
list>
property>
bean>
beans>
测试
public class SpringFactoryTest {
public static void main(String[] args) {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("bean.xml");
User user = ctx.getBean("User", User.class);
List<String> names = user.getFriends().getNames();
for (String name : names) {
System.out.println("FriendName: " + name);
}
ctx.close();
}
}
首先FactoryBean是一个Bean,但又不仅仅是一个Bean,这样听起来矛盾,但为啥又这样说呢?其实在Spring中,所有的Bean都是由BeanFactory(也就是IOC容器)来进行管理的。但对FactoryBean而言,这个FactoryBean不是简单的Bean,而是一个能生产或者修饰对象生成的工厂Bean,它的实现与设计模式中的工厂方法模式和修饰器模式类似
1) 为什么需要FactoryBean?
org.springframework.bean.factory.FactoryBean
的工厂类接口,用户可以通过实现该接口定制实例化Bean的逻辑。FactoryBean接口对于Spring框架来说占用重要的地位,Spring自身就提供了70多个FactoryBean的实现。它们隐藏了实例化一些复杂Bean的细节,给上层应用带来了便利。org.springframework.bean.factory.FactoryBean
接口,然后给出自己对象的实例化代码即可。2 ) FactoryBean的使用特点
当用户使用容器本身时,可以使用转义字符"&"来得到FactoryBean本身,以区别通过FactoryBean产生的实例对象和FactoryBean对象本身。
在BeanFactory中通过如下代码定义了该转义字符:
StringFACTORY_BEAN_PREFIX = "&";
举例
如果MyObject是一个FactoryBean,则使用&MyObject得到的是MyObject对象,而不是MyObject产生出来的对象。
3) FactoryBean的代码示例
@Configuration
@ComponentScan("com.example.factory_bean")
public class AppConfig {
}
@Component("studentBean")
public class StudentBean implements FactoryBean {
//返回工厂中的实例
@Override
public Object getObject() throws Exception {
//这里并不一定要返回MyBean自身的实例,可以是其他任何对象的实例。
return new TeacherBean();
}
//该方法返回的类型是在IOC容器中getBean所匹配的类型
@Override
public Class<?> getObjectType() {
return StudentBean.class;
}
public void study(){
System.out.println("学生学习......");
}
}
public class TeacherBean {
public void teach(){
System.out.println("老师教书......");
}
}
public class Test01 {
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
//StudentBean studentBean = (StudentBean)context.getBean("studentBean");
//加上&符号,返回工厂中的实例
// StudentBean studentBean = (StudentBean)context.getBean("&studentBean");
// studentBean.study();
TeacherBean teacherBean = (TeacherBean) context.getBean("studentBean");
teacherBean.teach();
}
}
3) FactoryBean源码分析
public interface FactoryBean<T> {
String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";
/**
getObject()方法: 会返回该FactoryBean生产的对象实例,我们需要实现该方法,以给出自己的对象实例化逻辑
这个方法也是FactoryBean的核心.
*/
@Nullable
T getObject() throws Exception;
/**
getObjectType()方法: 仅返回getObject() 方法所返回的对象类型,如果预先无法确定,返回NULL,
这个方法返回类型是在IOC容器中getBean所匹配的类型
*/
@Nullable
Class<?> getObjectType();
//该方法的结果用于表明 工厂方法getObject() 所生产的 对象是否要以单例形式存储在容器中如果以单例存在就返回true,否则返回false
default boolean isSingleton() {
return true;
}
}
FactoryBean表现的是一个工厂的职责,如果一个BeanA 是实现FactoryBean接口,那么A就是变成了一个工厂,根据A的名称获取到的实际上是工厂调用getObject()方法返回的对象,而不是对象本身,如果想获取工厂对象本身,需要在名称前面加上 '&'符号
使用场景
BeanFactory与FactoryBean区别
BeanFactory和ApplicationContext的区别
观察者模式它是用于建立一种对象与对象之间的依赖关系,一个对象发生改变时将自动通知其他对象,其他对象将相应的作出反应.
在观察者模式中发生改变的对象称为观察目标,而被通知的对象称为观察者,一个观察目标可以应对多个观察者,而且这些观察者之间可以没有任何相互联系,可以根据需要增加和删除观察者,使得系统更易于扩展.
观察者模式的别名有发布-订阅(Publish/Subscribe)模式, 我们来看一下观察者模式与发布订阅模式结构上的区别
观察者模式里,只有两个角色 —— 观察者 + 被观察者; 而发布订阅模式里,却不仅仅只有发布者和订阅者两个角色,还有一个管理并执行消息队列的 “经纪人Broker”
观察者和被观察者,是松耦合的关系;发布者和订阅者,则完全不存在耦合
观察者模式:数据源直接通知订阅者发生改变。
发布订阅模式:数据源告诉第三方(事件通道)发生了改变,第三方再通知订阅者发生了改变。
Spring 基于观察者模式,实现了自身的事件机制也就是事件驱动模型,事件驱动模型通常也被理解成观察者或者发布/订阅模型。
spring事件模型提供如下几个角色
1) 事件:ApplicationEvent
是所有事件对象的父类。ApplicationEvent 继承自 jdk 的 EventObject, 所有的事件都需要继承 ApplicationEvent, 并且通过 source 得到事件源。
public abstract class ApplicationEvent extends EventObject {
private static final long serialVersionUID = 7099057708183571937L;
private final long timestamp = System.currentTimeMillis();
public ApplicationEvent(Object source) {
super(source);
}
public final long getTimestamp() {
return this.timestamp;
}
}
Spring 也为我们提供了很多内置事件:
ContextRefreshEvent,当ApplicationContext容器初始化完成或者被刷新的时候,就会发布该事件。
ContextStartedEvent,当ApplicationContext启动的时候发布事件.
ContextStoppedEvent,当ApplicationContext容器停止的时候发布事件.
RequestHandledEvent,只能用于DispatcherServlet的web应用,Spring处理用户请求结束后,系统会触发该事件。
2) 事件监听:ApplicationListener
ApplicationListener(应用程序事件监听器) 继承自jdk的EventListener,所有的监听器都要实现这个接口,这个接口只有一个onApplicationEvent()方法,该方法接受一个ApplicationEvent或其子类对象作为参数
在方法体中,可以通过不同对Event类的判断来进行相应的处理.当事件触发时所有的监听器都会收到消息,如果你需要对监听器的接收顺序有要求,可是实现该接口的一个实现SmartApplicationListener,通过这个接口可以指定监听器接收事件的顺序.
@FunctionalInterface
public interface ApplicationListener<E extends ApplicationEvent> extends EventListener {
void onApplicationEvent(E var1);
}
实现了ApplicationListener接口之后,需要实现方法onApplicationEvent(),在容器将所有的Bean都初始化完成之后,就会执行该方法。
3) 事件源:ApplicationEventPublisher
事件的发布者,封装了事件发布功能方法接口,是Applicationcontext接口的超类
事件机制的实现需要三个部分,事件源,事件,事件监听器,在上面介绍的ApplicationEvent就相当于事件,ApplicationListener相当于事件监听器,这里的事件源说的就是ApplicationEventPublisher.
public interface ApplicationEventPublisher {
default void publishEvent(ApplicationEvent event) {
this.publishEvent((Object)event);
}
//调用publishEvent方法,传入一个ApplicationEvent的实现类对象作为参数,每当ApplicationContext发布ApplicationEvent时,所有的ApplicationListener就会被自动的触发.
void publishEvent(Object var1);
}
我们常用的ApplicationContext都继承了AbstractApplicationContext,像我们平时常见ClassPathXmlApplicationContext、XmlWebApplicationContex也都是继承了它,AbstractApplicationcontext是ApplicationContext接口的抽象实现类,在该类中实现了publishEvent方法:
protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
Assert.notNull(event, "Event must not be null");
if (this.earlyApplicationEvents != null) {
this.earlyApplicationEvents.add(applicationEvent);
} else {
//事件发布委托给applicationEventMulticaster
this.getApplicationEventMulticaster().multicastEvent((ApplicationEvent)applicationEvent, eventType);
}
}
在这个方法中,我们看到了一个getApplicationEventMulticaster().这就要牵扯到另一个类ApplicationEventMulticaster.
4) 事件管理:ApplicationEventMulticaster
用于事件监听器的注册和事件的广播。监听器的注册就是通过它来实现的,它的作用是把 Applicationcontext 发布的 Event 广播给它的监听器列表。
public interface ApplicationEventMulticaster {
//添加事件监听器
void addApplicationListener(ApplicationListener<?> var1);
//添加事件监听器,使用容器中的bean
void addApplicationListenerBean(String var1);
//移除事件监听器
void removeApplicationListener(ApplicationListener<?> var1);
void removeApplicationListenerBean(String var1);
//移除所有
void removeAllListeners();
//发布事件
void multicastEvent(ApplicationEvent var1);
void multicastEvent(ApplicationEvent var1, @Nullable ResolvableType var2);
}
在AbstractApplicationcontext中有一个applicationEventMulticaster的成员变量,提供了监听器Listener的注册方法.
public abstract class AbstractApplicationContext extends DefaultResourceLoader
implements ConfigurableApplicationContext, DisposableBean {
private ApplicationEventMulticaster applicationEventMulticaster;
protected void registerListeners() {
// Register statically specified listeners first.
for (ApplicationListener<?> listener : getApplicationListeners()) {
getApplicationEventMulticaster().addApplicationListener(listener);
}
// Do not initialize FactoryBeans here: We need to leave all regular beans
// uninitialized to let post-processors apply to them!
String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
for (String lisName : listenerBeanNames) {
getApplicationEventMulticaster().addApplicationListenerBean(lisName);
}
}
}
实现一个需求:当调用一个类的方法完成时,该类发布事件,事件监听器监听该类的事件并执行的自己的方法逻辑
假设这个类是Request、发布的事件是ReuqestEvent、事件监听者是ReuqestListener。当调用Request的doRequest方法时,发布事件。
代码如下
/**
* 定义事件
* @author spikeCong
* @date 2022/10/24
**/
public class RequestEvent extends ApplicationEvent {
public RequestEvent(Object source) {
super(source);
}
}
/**
* 发布事件
* @author spikeCong
* @date 2022/10/24
**/
@Component
public class Request {
@Autowired
private ApplicationContext applicationContext;
public void doRequest(){
System.out.println("调用Request类的doRequest方法发送一个请求......");
applicationContext.publishEvent(new RequestEvent(this));
}
}
/**
* 监听事件
* @author spikeCong
* @date 2022/10/24
**/
@Component
public class RequestListener implements ApplicationListener<RequestEvent> {
@Override
public void onApplicationEvent(RequestEvent requestEvent) {
System.out.println("监听到RequestEvent事件,执行本方法");
}
}
public class SpringEventTest {
public static void main(String[] args) {
ApplicationContext context =
new AnnotationConfigApplicationContext("com.mashibing.pubsub");
Request request = (Request) context.getBean("request");
//调用方法发布事件
request.doRequest();
}
}
//打印日志
调用Request类的doRequest方法发送一个请求......
监听到RequestEvent事件,执行本方法
上面代码的执行流程
监听器什么时候注册到IOC容器
注册的开始逻辑是在AbstractApplicationContext类的refresh方法,该方法包含了整个IOC容器初始化所有方法。其中有一个registerListeners()方法就是注册系统监听者(spring自带的)和自定义监听器的。
public void refresh() throws BeansException, IllegalStateException {
//BeanFactory准备工作完成后进行的后置处理工作
this.postProcessBeanFactory(beanFactory);
//执行BeanFactoryPostProcessor的方法;
this.invokeBeanFactoryPostProcessors(beanFactory);
//注册BeanPostProcessor(Bean的后置处理器),在创建bean的前后等执行
this.registerBeanPostProcessors(beanFactory);
//初始化MessageSource组件(做国际化功能;消息绑定,消息解析);
this.initMessageSource();
//初始化事件派发器
this.initApplicationEventMulticaster();
子类重写这个方法,在容器刷新的时候可以自定义逻辑;如创建Tomcat,Jetty等WEB服务器
this.onRefresh();
//注册应用的监听器。就是注册实现了ApplicationListener接口的监听器bean,这些监听器是注册到ApplicationEventMulticaster中的
this.registerListeners();
//初始化所有剩下的非懒加载的单例bean
this.finishBeanFactoryInitialization(beanFactory);
//完成context的刷新
this.finishRefresh();
}
看registerListeners的关键方法体,其中的两个方法addApplicationListener和addApplicationListenerBean
,从方法可以看出是添加监听者。
protected void registerListeners() {
Iterator var1 = this.getApplicationListeners().iterator();
while(var1.hasNext()) {
ApplicationListener<?> listener = (ApplicationListener)var1.next();
this.getApplicationEventMulticaster().addApplicationListener(listener);
}
String[] listenerBeanNames = this.getBeanNamesForType(ApplicationListener.class, true, false);
String[] var7 = listenerBeanNames;
int var3 = listenerBeanNames.length;
for(int var4 = 0; var4 < var3; ++var4) {
String listenerBeanName = var7[var4];
this.getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
}
}
那么最后将监听者放到哪里了呢?就是ApplicationEventMulticaster接口的子类
该接口主要两个职责,维护ApplicationListener相关类和发布事件。
实现在默认实现类AbstractApplicationEventMulticaster,最后将Listener放到了内部类ListenerRetriever两个set集合中
private class ListenerRetriever {
public final Set<ApplicationListener<?>> applicationListeners = new LinkedHashSet();
public final Set<String> applicationListenerBeans = new LinkedHashSet();
}
ListenerRetriever被称为监听器注册表。
Spring如何发布的事件并通知监听者
这个注意的有两个方法
1) publishEvent方法
AbstractApplicationContext
实现了ApplicationEventPublisher
接口的publishEvent
方法protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
Assert.notNull(event, "Event must not be null");
Object applicationEvent;
//尝试转换为ApplicationEvent或者PayloadApplicationEvent,如果是PayloadApplicationEvent则获取eventType
if (event instanceof ApplicationEvent) {
applicationEvent = (ApplicationEvent)event;
} else {
applicationEvent = new PayloadApplicationEvent(this, event);
if (eventType == null) {
eventType = ((PayloadApplicationEvent)applicationEvent).getResolvableType();
}
}
if (this.earlyApplicationEvents != null) {
//判断earlyApplicationEvents是否为空(也就是早期事件还没有被发布-说明广播器还没有实例化好),如果不为空则将当前事件放入集合
this.earlyApplicationEvents.add(applicationEvent);
} else {
//否则获取ApplicationEventMulticaster调用其multicastEvent将事件广播出去。本文这里获取到的广播器实例是SimpleApplicationEventMulticaster。
this.getApplicationEventMulticaster().multicastEvent((ApplicationEvent)applicationEvent, eventType);
}
//将事件交给父类处理
if (this.parent != null) {
if (this.parent instanceof AbstractApplicationContext) {
((AbstractApplicationContext)this.parent).publishEvent(event, eventType);
} else {
this.parent.publishEvent(event);
}
}
}
2) multicastEvent方法
继续进入到multicastEvent方法
,该方法有两种方式调用invokeListener,通过线程池和直接调用,进一步说就是通过异步和同步两种方式调用.
public void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType) {
//解析事件类型
ResolvableType type = eventType != null ? eventType : this.resolveDefaultEventType(event);
//获取执行器
Executor executor = this.getTaskExecutor();
// 获取合适的ApplicationListener,循环调用监听器的onApplicationEvent方法
Iterator var5 = this.getApplicationListeners(event, type).iterator();
while(var5.hasNext()) {
ApplicationListener<?> listener = (ApplicationListener)var5.next();
if (executor != null) {
//如果executor不为null,则交给executor去调用监听器
executor.execute(() -> {
this.invokeListener(listener, event);
});
} else {
//否则,使用当前主线程直接调用监听器;
this.invokeListener(listener, event);
}
}
}
3) invokeListener方法
// 该方法增加了错误处理逻辑,然后调用doInvokeListener
protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
ErrorHandler errorHandler = this.getErrorHandler();
if (errorHandler != null) {
try {
this.doInvokeListener(listener, event);
} catch (Throwable var5) {
errorHandler.handleError(var5);
}
} else {
this.doInvokeListener(listener, event);
}
}
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
//直接调用了listener接口的onApplicationEvent方法
listener.onApplicationEvent(event);
}
1) BeanFactory
BeanFactory作为最顶层的一个接口,定义了IoC容器的基本功能规范
从类图中我们可以发现最终的默认实现类是DefaultListableBeanFactory,它实现了所有的接口。那么为何要定义这么多层次的接口呢?
每个接口都有它的使用场合,主要是为了区分在Spring内部操作过程中对象的传递和转化,对对象的数据访问所做的限制。
例如,
这三个接口共同定义了Bean的集合、Bean之间的关系及Bean行为。
在BeanFactory里只对IoC容器的基本行为做了定义,根本不关心你的Bean是如何定义及怎样加载的。正如我们只关心能从工厂里得到什么产品,不关心工厂是怎么生产这些产品的。
2 ) ApplicationContext
BeanFactory有一个很重要的子接口,就是ApplicationContext接口,该接口主要来规范容器中的bean对象是非延时加载,即在创建容器对象的时候就对象bean进行初始化,并存储到一个容器中。
//延时加载
BeanFactory factory = new XmlBeanFactory(new ClassPathResource("bean.xml"));
//立即加载
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
User user = context.getBean("user", User.class);
ApplicationContext 的子类主要包含两个方面:
ConfigurableApplicationContext 表示该 Context 是可修改的,也就是在构建 Context 中用户可以动态添加或修改已有的配置信息
WebApplicationContext 顾名思义,就是为 web 准备的 Context 他可以直接访问到 ServletContext,通常情况下,这个接口使用少
要知道工厂是如何产生对象的,我们需要看具体的IoC容器实现,Spring提供了许多IoC容器实现,比如:
总体来说 ApplicationContext 必须要完成以下几件事:
3) Bean定义:BeanDefinition
这里的 BeanDefinition 就是我们所说的 Spring 的 Bean,我们自己定义的各个 Bean 其实会转换成一个个 BeanDefinition 存在于 Spring 的 BeanFactory 中
public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {
//DefaultListableBeanFactory 中使用 Map 结构保存所有的 BeanDefinition 信息
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
}
BeanDefinition 中保存了我们的 Bean 信息,比如这个 Bean 指向的是哪个类、是否是单例的、是否懒加载、这个 Bean 依赖了哪些 Bean 等等。
4) BeanDefinitionReader
Bean的解析过程非常复杂,功能被分得很细,因为这里需要被扩展的地方很多,必须保证足够的灵活性,以应对可能的变化。Bean的解析主要就是对Spring配置文件的解析。
这个解析过程主要通过BeanDefinitionReader来完成,看看Spring中BeanDefinitionReader的类结构图,如下图所示。
BeanDefinitionReader接口定义的功能
public interface BeanDefinitionReader {
/*
下面的loadBeanDefinitions都是加载bean定义,从指定的资源中
*/
int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException;
int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException;
int loadBeanDefinitions(String location) throws BeanDefinitionStoreException;
int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException;
}
5) BeanFactory后置处理器
后置处理器是一种拓展机制,贯穿Spring Bean的生命周期
后置处理器分为两类:
BeanFactory后置处理器:BeanFactoryPostProcessor
实现该接口,可以在spring的bean创建之前,修改bean的定义属性
public interface BeanFactoryPostProcessor {
/*
* 该接口只有一个方法postProcessBeanFactory,方法参数是ConfigurableListableBeanFactory,通过该
参数,可以获取BeanDefinition
*/
void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
}
6) Bean后置处理器:BeanPostProcessor
BeanPostProcessor是Spring IOC容器给我们提供的一个扩展接口
实现该接口,可以在spring容器实例化bean之后,在执行bean的初始化方法前后,添加一些处理逻辑
public interface BeanPostProcessor {
//bean初始化方法调用前被调用
Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
//bean初始化方法调用后被调用
Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}
对下面的配置文件进行解析,并自定义SpringIOC, 对涉及到的对象进行管理。
<beans>
<bean id="courseService" class="com.mashibing.service.impl.CourseServiceImpl">
<property name="courseDao" ref="courseDao">property>
bean>
<bean id="courseDao" class="com.mashibing.dao.impl.CourseDaoImpl">bean>
beans>
package com.mashibing.framework.beans;
/**
* 该类用来封装bean标签下的property子标签的属性
* 1.name属性
* 2.ref属性
* 3.value属性: 给基本数据类型及string类型数据赋的值
* @author spikeCong
* @date 2022/10/26
**/
n`,从方法可以看出是添加监听者。
```java
protected void registerListeners() {
Iterator var1 = this.getApplicationListeners().iterator();
while(var1.hasNext()) {
ApplicationListener<?> listener = (ApplicationListener)var1.next();
this.getApplicationEventMulticaster().addApplicationListener(listener);
}
String[] listenerBeanNames = this.getBeanNamesForType(ApplicationListener.class, true, false);
String[] var7 = listenerBeanNames;
int var3 = listenerBeanNames.length;
for(int var4 = 0; var4 < var3; ++var4) {
String listenerBeanName = var7[var4];
this.getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
}
}
那么最后将监听者放到哪里了呢?就是ApplicationEventMulticaster接口的子类
该接口主要两个职责,维护ApplicationListener相关类和发布事件。
实现在默认实现类AbstractApplicationEventMulticaster,最后将Listener放到了内部类ListenerRetriever两个set集合中
private class ListenerRetriever {
public final Set<ApplicationListener<?>> applicationListeners = new LinkedHashSet();
public final Set<String> applicationListenerBeans = new LinkedHashSet();
}
ListenerRetriever被称为监听器注册表。
Spring如何发布的事件并通知监听者
这个注意的有两个方法
1) publishEvent方法
AbstractApplicationContext
实现了ApplicationEventPublisher
接口的publishEvent
方法protected void publishEvent(Object event, @Nullable ResolvableType eventType) {
Assert.notNull(event, "Event must not be null");
Object applicationEvent;
//尝试转换为ApplicationEvent或者PayloadApplicationEvent,如果是PayloadApplicationEvent则获取eventType
if (event instanceof ApplicationEvent) {
applicationEvent = (ApplicationEvent)event;
} else {
applicationEvent = new PayloadApplicationEvent(this, event);
if (eventType == null) {
eventType = ((PayloadApplicationEvent)applicationEvent).getResolvableType();
}
}
if (this.earlyApplicationEvents != null) {
//判断earlyApplicationEvents是否为空(也就是早期事件还没有被发布-说明广播器还没有实例化好),如果不为空则将当前事件放入集合
this.earlyApplicationEvents.add(applicationEvent);
} else {
//否则获取ApplicationEventMulticaster调用其multicastEvent将事件广播出去。本文这里获取到的广播器实例是SimpleApplicationEventMulticaster。
this.getApplicationEventMulticaster().multicastEvent((ApplicationEvent)applicationEvent, eventType);
}
//将事件交给父类处理
if (this.parent != null) {
if (this.parent instanceof AbstractApplicationContext) {
((AbstractApplicationContext)this.parent).publishEvent(event, eventType);
} else {
this.parent.publishEvent(event);
}
}
}
2) multicastEvent方法
继续进入到multicastEvent方法
,该方法有两种方式调用invokeListener,通过线程池和直接调用,进一步说就是通过异步和同步两种方式调用.
public void multicastEvent(ApplicationEvent event, @Nullable ResolvableType eventType) {
//解析事件类型
ResolvableType type = eventType != null ? eventType : this.resolveDefaultEventType(event);
//获取执行器
Executor executor = this.getTaskExecutor();
// 获取合适的ApplicationListener,循环调用监听器的onApplicationEvent方法
Iterator var5 = this.getApplicationListeners(event, type).iterator();
while(var5.hasNext()) {
ApplicationListener<?> listener = (ApplicationListener)var5.next();
if (executor != null) {
//如果executor不为null,则交给executor去调用监听器
executor.execute(() -> {
this.invokeListener(listener, event);
});
} else {
//否则,使用当前主线程直接调用监听器;
this.invokeListener(listener, event);
}
}
}
3) invokeListener方法
// 该方法增加了错误处理逻辑,然后调用doInvokeListener
protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
ErrorHandler errorHandler = this.getErrorHandler();
if (errorHandler != null) {
try {
this.doInvokeListener(listener, event);
} catch (Throwable var5) {
errorHandler.handleError(var5);
}
} else {
this.doInvokeListener(listener, event);
}
}
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
//直接调用了listener接口的onApplicationEvent方法
listener.onApplicationEvent(event);
}
1) BeanFactory
BeanFactory作为最顶层的一个接口,定义了IoC容器的基本功能规范
从类图中我们可以发现最终的默认实现类是DefaultListableBeanFactory,它实现了所有的接口。那么为何要定义这么多层次的接口呢?
每个接口都有它的使用场合,主要是为了区分在Spring内部操作过程中对象的传递和转化,对对象的数据访问所做的限制。
例如,
这三个接口共同定义了Bean的集合、Bean之间的关系及Bean行为。
在BeanFactory里只对IoC容器的基本行为做了定义,根本不关心你的Bean是如何定义及怎样加载的。正如我们只关心能从工厂里得到什么产品,不关心工厂是怎么生产这些产品的。
2 ) ApplicationContext
BeanFactory有一个很重要的子接口,就是ApplicationContext接口,该接口主要来规范容器中的bean对象是非延时加载,即在创建容器对象的时候就对象bean进行初始化,并存储到一个容器中。
//延时加载
BeanFactory factory = new XmlBeanFactory(new ClassPathResource("bean.xml"));
//立即加载
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
User user = context.getBean("user", User.class);
ApplicationContext 的子类主要包含两个方面:
ConfigurableApplicationContext 表示该 Context 是可修改的,也就是在构建 Context 中用户可以动态添加或修改已有的配置信息
WebApplicationContext 顾名思义,就是为 web 准备的 Context 他可以直接访问到 ServletContext,通常情况下,这个接口使用少
要知道工厂是如何产生对象的,我们需要看具体的IoC容器实现,Spring提供了许多IoC容器实现,比如:
总体来说 ApplicationContext 必须要完成以下几件事:
3) Bean定义:BeanDefinition
这里的 BeanDefinition 就是我们所说的 Spring 的 Bean,我们自己定义的各个 Bean 其实会转换成一个个 BeanDefinition 存在于 Spring 的 BeanFactory 中
public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory
implements ConfigurableListableBeanFactory, BeanDefinitionRegistry, Serializable {
//DefaultListableBeanFactory 中使用 Map 结构保存所有的 BeanDefinition 信息
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
}
BeanDefinition 中保存了我们的 Bean 信息,比如这个 Bean 指向的是哪个类、是否是单例的、是否懒加载、这个 Bean 依赖了哪些 Bean 等等。
4) BeanDefinitionReader
Bean的解析过程非常复杂,功能被分得很细,因为这里需要被扩展的地方很多,必须保证足够的灵活性,以应对可能的变化。Bean的解析主要就是对Spring配置文件的解析。
这个解析过程主要通过BeanDefinitionReader来完成,看看Spring中BeanDefinitionReader的类结构图,如下图所示。
BeanDefinitionReader接口定义的功能
public interface BeanDefinitionReader {
/*
下面的loadBeanDefinitions都是加载bean定义,从指定的资源中
*/
int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException;
int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException;
int loadBeanDefinitions(String location) throws BeanDefinitionStoreException;
int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException;
}
5) BeanFactory后置处理器
后置处理器是一种拓展机制,贯穿Spring Bean的生命周期
后置处理器分为两类:
BeanFactory后置处理器:BeanFactoryPostProcessor
实现该接口,可以在spring的bean创建之前,修改bean的定义属性
public interface BeanFactoryPostProcessor {
/*
* 该接口只有一个方法postProcessBeanFactory,方法参数是ConfigurableListableBeanFactory,通过该
参数,可以获取BeanDefinition
*/
void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException;
}
6) Bean后置处理器:BeanPostProcessor
BeanPostProcessor是Spring IOC容器给我们提供的一个扩展接口
实现该接口,可以在spring容器实例化bean之后,在执行bean的初始化方法前后,添加一些处理逻辑
public interface BeanPostProcessor {
//bean初始化方法调用前被调用
Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException;
//bean初始化方法调用后被调用
Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException;
}
对下面的配置文件进行解析,并自定义SpringIOC, 对涉及到的对象进行管理。
<beans>
<bean id="courseService" class="com.mashibing.service.impl.CourseServiceImpl">
<property name="courseDao" ref="courseDao">property>
bean>
<bean id="courseDao" class="com.mashibing.dao.impl.CourseDaoImpl">bean>
beans>
package com.mashibing.framework.beans;
/**
* 该类用来封装bean标签下的property子标签的属性
* 1.name属性
* 2.ref属性
* 3.value属性: 给基本数据类型及string类型数据赋的值
* @author spikeCong
* @date 2022/10/26
**/
public class Pro