模板方法模式
定义一个操作中的算法骨架,而将一些步骤延迟到子类中,模板方法使得子类可以不改变一个算法的结构即可重新定义该算法的某些特定步骤。抽象工厂模式
是一种为调用者(客户端)提供一个创建一组相关或相互依赖的对象
的接口,且调用者无需指定所要产品的具体类就能得到同族不同等级的产品
的模式结构。而这两种设计模式在一起使用,彼此交融,将会碰撞出什么样的火花呢?
模板方法模式
定义一个操作中的算法骨架,而将一些步骤延迟到子类中,模板方法使得子类可以不改变一个算法的结构即可重新定义该算法的某些特定步骤。抽象工厂模式
是一种为调用者(客户端)提供一个创建一组相关或相互依赖的对象
的接口,且调用者无需指定所要产品的具体类就能得到同族不同等级的产品
的模式结构。而这两种设计模式在一起使用,彼此交融,将会碰撞出什么样的火花呢?先回顾一下这两个设计模式吧
设计模式回顾 模板方法模式
模板方法模式
定义一个操作中的算法骨架,而将一些步骤延迟到子类中,模板方法使得子类可以不改变一个算法的结构即可重新定义该算法的某些特定步骤。
在不使用设计模式时,一个算法的实现中,因算法步骤中部分逻辑变更,可能会因只改变某几行代码逻辑,而复制整合算法结构的代码。这样会造成大量重复代码,不易维护。而使用模板方法模式
,则只需创建一个调用器(invoker
)并向外暴露一个调用器方法(invoke
),将算法的各个步骤封装到单独的方法;将算法的骨架(执行顺序、执行逻辑)封装在调用器方法中即可。因调用器不能完全了解每个步骤的实现,算法的部分步骤可以在该类中提供默认的实现,让子类选择性覆盖;而某些步骤必须让子类实现才可以完成,则对应步骤的方法就应该设置为抽象(称之为:钩子函数),对应的类也应该设置为抽象。
类图 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 classDiagram Invoker <|-- CustomInvokerA Invoker <|-- CustomInvokerB Invoker <-- Client class Client{ -Invoker invoker } class Invoker{ <<abstract>> +final invoke #customFunctionA() #customFunctionB() } class CustomInvokerA{ +customFunctionA() +customFunctionB() } class CustomInvokerB{ +customFunctionA() +customFunctionB() }
模板方法类图
CustomInvokerA
与CustomInvokerB
继承Invoker
,并实现customFunction()
方法。Client
为客户端,内部持有一个Invoker
对象,通过切换Invoker
对象对象,从而切换算法的不同实现。
代码实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 public abstract class Invoker { public final void invoke () { customFunctionA(); customFunctionB(); } protected void customFunctionA () { } protected abstract void customFunctionB () ; } public class CustomInvokerA { @Override public void customFunctionB () { } } public class CustomInvokerA { @Override public void customFunctionA () { } @Override public void customFunctionB () { } } public class Client { private Invoker invoker; public Client (Invoker invoker) { this .invoker = invoker; } public void invoke () { this .invoker.invoke(); } }
抽象工厂模式
抽象工厂模式
为调用者(客户端)提供一个创建一组相关或相互依赖的对象
的接口,且调用者无需指定所需产品的具体类就能得到同族不同等级的产品
的模式结构。
抽象工厂模式
可以说是工程方法模式
的升级版,虽然工程方法模式
的工厂类可以提供多个工厂方法,但通过多个工厂方法获取的多个对象之间并无联系,或者说获取的这些对象之间彼此无依赖。而抽象工厂模式
的工厂类中提供的工厂方法所生产的产品都具有相关性或彼此依赖,其提供的是一套产品生产的流水线,客户端只需切换产品流水线,即可切换整个产品族。抽象工厂模式
中有这么几个概念:
抽象工厂
(Abstract Factory) : 提供产品线生产产品的抽象描述
具体工厂
(Concrete Factory) : 抽象工厂的具体实现,负责产品线上每个产品的具体实现
抽象产品
(Abstract Product) : 产品的抽象描述
具体产品
(Concrete Product) : 抽象产品的具体实现
类图 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 classDiagram AbstractProductA "many"<--"1" AbstractFactory : 依赖 AbstractProductB "many"<--"1" AbstractFactory : 依赖 AbstractFactory <|-- ConcreteFactoryA AbstractFactory <|-- ConcreteFactoryB AbstractProductA <|-- ConcreteProductA1 AbstractProductA <|-- ConcreteProductA2 AbstractProductB <|-- ConcreteProductB1 AbstractProductB <|-- ConcreteProductB2 Client -->AbstractProductA Client -->AbstractProductB Client -->AbstractFactory class AbstractFactory{ <<abstract>> +getProductA() +getProductB() } class ConcreteFactoryA{ +getProductA() +getProductB() } class ConcreteFactoryB{ +getProductA() +getProductB() } class AbstractProductA{ <<abstract>> +productAFunctionA() +productAFunctionB() } class AbstractProductB{ <<abstract>> } class ConcreteProductA1{ +ConcreteProductA1() } class ConcreteProductA2{ +ConcreteProductA2() } class ConcreteProductB1{ +ConcreteProductB1() } class ConcreteProductB2{ +ConcreteProductB2() } class Client{ -AbstractFactory factory +AbstractProductA createProdcutA() +AbstractProductB createProdcutB() }
代码实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 public class Client { private AbstractFactory factory; public Client (AbstractFactory factory) { this .factory = factory; } public void doSomething () { AbstractProductA productA = factory.getProductA(); AbstractProductB productB = factory.getProductB(); } } public abstract class AbstractFactory { public AbstractProductA getProductA () ; public AbstractProductB getProductB () ; } public class ConcreteFactoryA extends AbstractFactory { public AbstractProductA getProductA () { return new ConcreteProductA1 (); } public AbstractProductB getProductB () { return new ConcreteProductB1 (); } } public class ConcreteFactoryB extends AbstractFactory { public AbstractProductA getProductA () { return new ConcreteProductA2 (); } public AbstractProductB getProductB () { return new ConcreteProductB2 (); } } public abstract class AbstractProductA { public AbstractProductA () {} } public class ConcreteProductA1 extends AbstractProductA { public ConcreteProductA1 () {} } public class ConcreteProductA2 extends AbstractProductA { public ConcreteProductA2 () {} } public abstract class AbstractProductB { public AbstractProductB () {} } public class ConcreteProductB1 extends AbstractProductB { public ConcreteProductB1 () {} } public class ConcreteProductB2 extends AbstractProductB { public ConcreteProductB2 () {} }
组合思路 模板方法模式
封装算法,对算法特定步骤下沉到子类实现;而抽象工厂模式
可以提供同族不同级别的产品。如果对模板方法模式
的所有算法都包装成对象,提供给调用器方法,如果想要变更算法,就需要切换一组算法对象,而切换一组相互关联的对象,就可以使用抽象工厂模式
来实现。回过头来看,模板方法模式
用来封装算法,而抽象工厂模式
用来提供算法某些步骤的实现,这是个好主意。(Perfect!)
组合类图及分析 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 classDiagram Invoker <-- Client AbstractHandler "one|many"<--"one" Invoker : 依赖 AbstractHandler <|-- ConcreteHandlerA : 继承 AbstractHandler <|-- ConcreteHandlerB : 继承 class Client{ -Invoker invoker } class Invoker{ -AbstractHandler handler +final invoke() } class AbstractHandler{ <<abstract>> +beforeHandle(req) +doHandle(req) +afterHandle(req) +throwHandle(req,exception) } class ConcreteHandlerA{ +beforeHandle(req) +doHandle(req) +afterHandle(req) +throwHandle(req,exception) } class ConcreteHandlerB{ +beforeHandle(req) +doHandle(req) +afterHandle(req) +throwHandle(req,exception) }
在上述模块架构图中,Invoker
接口为调用器,负责实现模板方法模式
中的算法骨架封装。AbstractHandler
抽象类为处理器,封装了算法骨架中某些步骤的实际执行逻辑,通过提供不同的子类处理器,实现对算法步骤的整体替换。Invoker
依赖一个或多个AbstractHandler
,根据业务场景不同,选用AbstractHandler
的依赖个数也不同。
若算法已经定义好了标准实现,后期如果想对算法进行扩展或修改,此时最优的做法就是扩展AbstractHandler
抽象类,实现算法步骤的修改,并替换Invoker
调用器中原有的处理器。
若算法在某计算节点的分支算法较多(例如:支付预下单
、订单结算
、支付信息回调
或数据解析
等),算法分支的切换依赖于入参的类型,这时为Invoker
提供多个AbstractHandler
实现,在算法骨架中,根据入参类型选用特定的处理器,从而实现算法步骤的切换,是最灵活且最优的解决方案。
操练一下 需求描述 大连东软信息学院计划对“素质教育系统”进行改造升级,将系统接入微信服务号,师生关注对应的服务号即可使用相关的功能,例如:学分查询、学分申报、成绩查询、课程安排等。一期需求,计划首先将学分查询功能迁移到微信服务号内,学分分类包括素质学分
和实践学分
,学生在服务号完成校内账号绑定操作后,发送关键字素质学分查询
或课程学分查询
,系统自动以模板消息的形式返回学生的学分。 目前账号绑定及相关的查询API已由别的系统封装好,只需对消息进行处理,返回对应的信息即可。
需求分析 用户
已经关注微信服务号并与已经学员账号绑定,此时通过微信服务号聊天窗口编辑并发送关键字素质学分查询
或课程学分查询
。微信通过服务号后台配置的消息接收地址及相应加密信息,对消息进行加密并发送给服务端。服务端接收到消息后,对消息的来源做验证,以保证此消息是从微信服务端发出。消息合法性校验通过后,进行消息解密,对解密后的内容做判断,若消息内容为素质学分查询
,则调用下游系统素质学分查询API
,若消息内容为课程学分查询
,则调用下游系统课程学分查询API
,以此类推。对查询后消息结果进行处理,选择适当的微信模板消息模板,填充消息内容,通过微信服务号API发送给用户,此时用户端的聊天界面会以微信模板消息形式展示学分信息。
流程图 下图按系统内外部对整个消息处理过程简单描述
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 graph TB; subgraph 系统外部 Client[微信客户端]-->|1-微信客户端发送消息|Wechat[微信] end subgraph 系统内部 Wechat[微信]-->|对消息加密发送给服务端|MessageRouter[消息处理] MessageRouter-->|判断消息来源是否是微信|checkA[消息来源是否合法] checkA-->|不合法|抛异常 checkA-->|合法|messageDecode[消息解密] messageDecode[消息解密]-->|失败|抛异常 messageDecode[消息解密]-->|成功|checkB[消息内容判断] checkB-->|msg=素质学分查询|调用素质学分查询API checkB-->|msg=课程学分查询|调用课程学分查询API subgraph 业务处理 调用素质学分查询API-->封装查询结果 调用课程学分查询API-->封装查询结果 end 抛异常-->统一异常处理-->封装返回 封装查询结果-->封装返回 封装返回-->|处理成功|Wechat 封装返回-->|处理失败|checkC[是否发送微信消息] checkC-->|是|选择适当消息类型-->发送微信消息 checkC-->|否|结束 发送模板消息-->发送微信消息 end 发送微信消息-->Wechat-->|微信服务端推送消息给客户端|Client
业务流程图
微信客户端Client
(以下简称客户端
)向微信服务号(Wechat
微信)发送消息(PS:消息可以是在微信官方文档已知支持的所有类型),微信接收到客户端向服务号发送的消息后,读取微信服务号的后台消息配置(消息接收地址、加密秘钥、Token等),将用户发送的消息加密,以HTTP POST方式将加密后的消息文本发送到后台配置的服务端消息接收地址。
服务端消息接收地址收到请求后,对请求的来源和数据的合法性进行校验
根据消息类型和内容的不同,执行不同的处理逻辑。eg:文本消息且消息内容为”素质学分查询”,则调用调用素质学分查询API查询学分,通过微信消息机制发送给客户端。
消息正确处理结束后需返回true,表示该消息服务端已经正确处理。否则,微信会按照文档约定的间隔时间和次数重发消息。
类图 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 classDiagram WxMsgController "one"--> "one" WxMsgService : 关联 WxMsgService "one"--> "one" WechatApi : 关联 WxMsgService "one"--> "one" WxMessageProcessor : 关联 ApplicationContextAware <|-- WxMessageProcessor : 实现 WxMessageProcessor "one"--> "many" IWxMessageHandler : 关联 IWxMessageHandler <|-- BaseWxMessageHandler : 实现 BaseWxMessageHandler <|-- BaseWxTextMessageHandler : 继承 BaseWxMessageHandler <|-- BaseWxImageMessageHandler : 继承 BaseWxMessageHandler <|-- BaseWxVideoMessageHandler : 继承 BaseWxTextMessageHandler <|-- QualityCreditsQueryWxTextMessageHandler : 继承 BaseWxTextMessageHandler <|-- PracticalCreditsQueryWxTextMessageHandler : 继承 QualityCreditsQueryWxTextMessageHandler "one"--> "one" QualityApi : 关联 PracticalCreditsQueryWxTextMessageHandler "one"--> "one" QualityApi : 关联 class WxMsgController{ -WxMsgService wxMsgService +String processMessage(String message) } class WxMsgService{ -WxMessageProcessor wxMessageProcessor -WechatApi wechatApi +String processMessage(String message) -void sendWxMsg(WxMsg wxMsg) } class ApplicationContextAware{ <<interface>> #void setApplicationContext(ApplicationContext applicationContext) } class WxMessageProcessor{ -Map<String, List<IWxMessageHandler>> messageHandlerMap -ApplicationContext applicationContext +void init() +void final setApplicationContext(ApplicationContext applicationContext) +final String processMessage(String message) } class IWxMessageHandler{ <<interface>> +String getCanHandleMsgType() +Boolean canHandle(WxMsg msg) +Result beforeHandle(WxMsg msg) +Result doHandle(WxMsg msg) +Result afterHandle(WxMsg msg) +void throwHandle(WxMsg msg, Throwable e) } class BaseWxMessageHandler{ <<abstract>> +String getCanHandleMsgType() +Boolean canHandle(WxMsg msg) +Result beforeHandle(WxMsg msg) +Result doHandle(WxMsg msg) +Result afterHandle(WxMsg msg) +void throwHandle(WxMsg msg, Throwable e) } class BaseWxTextMessageHandler{ <<abstract>> +final String getCanHandleMsgType() } class BaseWxImageMessageHandler{ <<abstract>> +final String getCanHandleMsgType() } class BaseWxVideoMessageHandler{ <<abstract>> +final String getCanHandleMsgType() } class QualityCreditsQueryWxTextMessageHandler{ -QualityApi qualityApi +Boolean canHandle(WxMsg msg) +Result beforeHandle(WxMessage msg) +Result doHandle(WxMessage msg) } class PracticalCreditsQueryWxTextMessageHandler{ -QualityApi qualityApi +Boolean canHandle(WxMsg msg) +Result beforeHandle(WxMessage msg) +Result doHandle(WxMessage msg) } class QualityApi{ -HttpClient httpClient +Object queryQualityCredits(params) +Object queryPracticalCredits(params) } class WechatApi{ -HttpClient httpClient +WxMsg wxMsgDecode(String message) +String wxMsgEncode(WxMsg msg) +void sendWxMsg(WxMsg wxMsg) -void sendWxTemplateMsg(WxMsg wxMsg) -void sendWxSimpleMsg(WxMsg wxMsg) }
WechatApi
和QualityApi
分别是对微信API和素质教育系统API的封装,基于HTTP请求。
Demo代码基于Spring框架开发,WxMsgController
是微信消息处理控制器,暴露了一个API,供接收微信消息推送请求,内部依赖WxMsgService
。
WxMsgService
是微信消息处理业务实现
String processMessage(String message)
方法:负责对微信发送过来的加密消息进行处理,返回以XML数据结构。
WxMessageProcessor
负责实际处理微信消息,他是一个具体的类。这个类实现了ApplicationContextAware
接口
void setApplicationContext(ApplicationContext applicationContext)
方法:在SpringIoC容器启动后,会调用实现ApplicationContextAware
该接口类的该方法,注入ApplicationContext
依赖。
void nit()
方法:该方法被@PostConstructor
注解标注,是Bean的初始化方法,用做Spring Bean的初始化生命周期回调,在Spring完成对WxMessageProcessor
的实例化和依赖项注入之后,会立刻调用该Bean的初始化生命周期方法,在该方法内部,通过注入的ApplicationContext
获取所有类型为IWxMessageHandler
的Bean,并对他们进行分组,并保存到处理器高速缓存当中备用。
final String processMessage(String message)
方法:负责封装消息处理的算法骨架,他实现了模板方法模式中对算法骨架封装的方法,且该方法被标注为final
,表示该方法不可以被子类覆盖,即保证了子类不会破坏算法的核心骨架,在该方法内部,通过微信消息的类型和消息内容,从处理器高速缓存当中选择合适的处理器进行消息处理,如果未找到合适的处理器,则会抛NotFoundMatchdWxMsgHandlerException
异常,具体的异常处理逻辑,其实是对本次微信消息推送请求返回了false并打印日志。此处不讨论异常处理的具体细节。
IWxMessageHandler
是消息处理器的抽象接口,它定义了消息处理器对消息的处理步骤。可以把它就理解为消息处理器,对于WxMessageProcessor
来说,他只依赖IWxMessageHandler
接口抽象,并不依赖与具体的处理器对象。
BaseWxMessageHandler
实现了IWxMessageHandler
接口,是消息处理器的抽象实现,提供了公共方法和部分处理器方法的默认实现,子类可选择性覆盖。
BaseWxTextMessageHandler
、BaseWxImageMessageHandler
和BaseWxVideoMessageHandler
都继承了BaseWxMessageHandler
,是特定微信消息类型的消息处理器抽象实现。例如:BaseWxTextMessageHandler
用于处理文本消息,BaseWxImageMessageHandler
用于处理图片消息,BaseWxVideoMessageHandler
用于处理视频消息等,具体可以参照微信官方文档消息管理中提供的已知消息类型。
final String getCanHandleMsgType()
方法:该方法制定了可处理该类型消息的处理器本身所具备的特点,就是只负责处理本类型微信消息
QualityCreditsQueryWxTextMessageHandler
和PracticalCreditsQueryWxTextMessageHandler
分别是素质学分查询
和实践学分查询
消息的处理器具体实现。在该处理器实现中,调用了QualityApi
相应的查询接口获取业务查询结果。
时序图 系统启动加载时序图 1 2 3 4 5 6 7 8 sequenceDiagram SpringIoC容器 ->> WxMessageProcessor: 调用setApplicationContext注入ApplicationContext SpringIoC容器 ->> WxMessageProcessor: 执行其初始化声明周期回调方法init WxMessageProcessor ->> ApplicationContext: 调用getBean方法获取所有类型为IWxMessageHandler的Bean ApplicationContext -->> WxMessageProcessor: 返回Bean loop Note over WxMessageProcessor: 将处理器放入缓存 end
在Spring容器启动时,通过ApplicationContextAware
接口的setApplicationContext
方法注入ApplicationContext
依赖,并调用当前Bean的初始化生命周期方法,通过ApplicationContext
加载所有IWxMessageHandler
类型的Bean实例,即获取所有消息处理器对象,并对消息处理器进行分组。
消息处理时序图 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 sequenceDiagram Client->> Wechat: 向微信服务号发送消息 Wechat ->> DispatcherServlet: 使用POST请求将微信消息message发送给服务端 DispatcherServlet ->> Filter: 校验微信消息请求来源合法性 alt 不合法 Filter ->> WxErrorHandler: 抛异常(忽略后续处理) end DispatcherServlet ->> WxMsgController: 执行处理器方法 WxMsgController ->> WxMsgService: 调用processMessage方法处理消息并返回结果 WxMsgService ->> WechatApi: 调用wxMsgDecode方法解密微信消息 alt 解密失败 WechatApi ->> WxErrorHandler: 抛异常(忽略后续处理) end WechatApi -->> WxMsgService: 返回微信消息实体WxMsg WxMsgService ->> WxMessageProcessor: 调用processMessage方法 WxMessageProcessor ->> WxMessageProcessor: 选择适当的消息处理器 alt 无匹配的消息处理器 WxMessageProcessor ->> WxErrorHandler: 抛异常(忽略后续处理) end rect rgb(0, 0, 255, .1) Note over WxErrorHandler,IWxMessageHandler: 调用消息处理器完成处理过程 WxMessageProcessor ->> IWxMessageHandler: 调用beforeHandle方法 alt beforeHandle方法返回失败 WxMessageProcessor ->> WxErrorHandler: 抛异常(忽略后续处理) end WxMessageProcessor ->> IWxMessageHandler: 调用doHandle方法 alt doHandle方法返回失败 WxMessageProcessor ->> WxErrorHandler: 抛异常(忽略后续处理) end WxMessageProcessor ->> IWxMessageHandler: 调用afterHandle方法 alt afterHandle方法返回失败 WxMessageProcessor ->> WxErrorHandler: 抛异常(忽略后续处理) end end alt 需要给用户发送微信回执消息 WxMessageProcessor ->> WxMessageProcessor: 组装消息结果 WxMessageProcessor ->> WechatApi: 调用sendWxMsg方法 WechatApi ->> Wechat: 将消息发送给微信服务器 Wechat ->> Client: 消息推送给微信客户端 end WxMessageProcessor -->> WxMsgService: 返回处理结果 WxMsgService -->> WxMsgController: 返回true WxMsgController -->> Wechat: 返回true,微信收到true表示消息处理成功
代码实现 因篇幅原因,对WxMsgController
、WechatApi
和QualityApi
的代码不做描述。
WxMsgService
业务逻辑类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 @Component public class WxMsgService { @Resource private WxMessageProcessor wxMessageProcessor; @Resource private WechatApi wechatApi; @Transactional(rollbackFor = Exception.class) public String processMessage (String message) { WxMsg wxMsg = wechatApi.wxMsgDecode(message); ProcessResult processResult = wxMessageProcessor.processMessage(wxMsg); if (!processResult.getStatus()){ return "false" ; } if (processResult.needSendWxMessage()){ wechatApi.sendWxMsg(processResult.getResultWxMsg()); } return "true" ; } private void sendWxMsg (WxMsg wxMsg) { this .wechatApi.sendWxMsg(wxMsg); } }
IWxMessageHandler
消息处理器抽象接口
1 2 3 4 5 6 7 8 9 public interface IWxMessageHandler { String getCanHandleMsgType () ; Boolean canHandle (WxMsg msg) ; Result beforeHandle (WxMsg msg) ; Result doHandle (WxMsg msg) ; Result afterHandle (WxMsg msg) ; void throwHandle (WxMsg msg, Throwable e) ; }
BaseWxMessageHandler
抽象消息处理器,实现IWxMessageHandler
接口,提供工具方法和部分处理器方法默认实现,子类可以选择性覆盖。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public abstract class BaseWxMessageHandler implements IWxMessageHandler { @Override public Result beforeHandle (WxMsg msg) { return ResultUtil.defaultSuccess(); } @Override public Result afterHandle (WxMsg msg) { return ResultUtil.defaultSuccess(); } @Override public void throwHandle (WxMsg msg, Throwable e) { throw e; } }
BaseWxTextMessageHandler
、BaseWxImageMessageHandler
、BaseWxVideoMessageHandler
等,针对微信消息的类型,提供不同的消息类型处理器抽象。如果要对新的消息类型进行处理扩展,则可以继承BaseWxMessageHandler
类。每个消息类型处理器都包含了一组本类型的处理器集合,例如BaseWxTextMessageHandler
处理器的所有子类,默认处理文本消息类型的微信消息,可以选择根据消息内容进行消息处理。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 public abstract class BaseWxTextMessageHandler extends BaseWxMessageHandler { @Override public final String getCanHandleMsgType () { return "text" ; } } public abstract class BaseWxImageMessageHandler extends BaseWxMessageHandler { @Override public final String getCanHandleMsgType () { return "image" ; } } public abstract class BaseWxVideoMessageHandler extends BaseWxMessageHandler { @Override public final String getCanHandleMsgType () { return "video" ; } }
QualityCreditsQueryWxTextMessageHandler
和PracticalCreditsQueryWxTextMessageHandler
都继承BaseWxTextMessageHandler
类,是消息处理器的具体实现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 @Component public class QualityCreditsQueryWxTextMessageHandler extends BaseWxTextMessageHandler { @Resource private QualityApi qualityApi; @Override public Boolean canHandle (WxMsg msg) { return "素质学分查询" .equals(msg.getContent); } @Override public Result beforeHandle (WxMessage msg) { qualityApi.checkWx(msg.getOpenId()); return ResultUtil.defaultSuccess(); } @Override public Result doHandle (WxMessage msg) { QualityCredits credits = qualityApi.queryQualityCredits(msg.getOpenId()); if (Objects.isNull(credits)){ return ResultUtil.defaultFailure("素质学分查询失败" ); } return ResultUtil.defaultSuccess(credits); } } @Component public class PracticalCreditsQueryWxTextMessageHandler extends BaseWxTextMessageHandler { @Resource private QualityApi qualityApi; @Override public String getCanHandleMsgType () { return "text" ; } @Override public Boolean canHandle (WxMsg msg) { return "实践学分查询" .equals(msg.getContent); } @Override public Result beforeHandle (WxMessage msg) { qualityApi.checkWx(msg.getOpenId()); return ResultUtil.defaultSuccess(); } @Override public Result doHandle (WxMessage msg) { QualityCredits credits = qualityApi.queryPracticalCredits(msg.getOpenId()); if (Objects.isNull(credits)){ return ResultUtil.defaultFailure("实践学分查询失败" ); } return ResultUtil.defaultSuccess(credits); } }
WxMessageProcessor
类实现ApplicationContextAware
接口,覆盖该接口的setApplicationContext
方法,在Spring容器启动时注入ApplicationContext
依赖。该类还持有一个名为handlerMap
类型为 Map<String, List<IWxMessageHandler>>
的成员属性,用来维护不同消息类型的所有消息处理器缓存。并且该类init
方法为SpringBean对象的初始化生命周期回调函数,在Spring IoC容器完成对Bean的实例化以及Bean依赖项注入之后,立即调用初始化生命周期方法。该方法被调用时,若消息处理器缓存为空,则加载消息处理器,通过ApplicationContext
的getBeansOfType
方法获取所有IWxMessageHandler
类型的Bean,即拿到所有消息处理器,然后依据处理器可处理的消息类型进行分组。 在调用processMessage
方法对消息进行处理时,首先根据消息类型在处理器缓存中选择该消息类型的处理器列表,对列表进行遍历,若找到一个可以处理该消息的处理器,就使用该处理器进行消息处理,并返回。否则,抛出NotFoundMatchdWxMsgHandlerException
异常,表示未找到可处理该消息的消息处理器,微信消息统一异常处理器在捕获该类型异常后,会向微信用户端返回类似“不支持的消息”提示,异常处理的详细信息不再阐述。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 @Component public class WxMessageProcessor implements ApplicationContextAware { private Map<String, List<IWxMessageHandler>> handlerMap; private ApplicationContext applicationContext; @Override public final void setApplicationContext (ApplicationContext applicationContext) throws BeansException { this .applicationContext = applicationContext; } @PostConstruct public final void init () { if (MapUtils.isEmpty(this .handlerMap)) { this .handlerMap = Maps.newHashMap(); handlerMap = applicationContext.getBeansOfType(IWxMessageHandler.class).values().stream().collect(Collectors.groupingBy(IWxMessageHandler::getCanHandleMsgType)); } } public final ProcessResult processMessage (WxMsg wxMsg) { IWxMessageHandler handler = this .selectHandler(wxMsg); if (Objects.isNull(handler)){ throw new NotFoundMatchdWxMsgHandlerException ("未找到匹配的消息处理器" ); } ProcessResult result = new ProcessResult (); try { Result beforeHandleResult = handler.beforeHandle(wxMsg); checkResult(beforeHandleResult); Result doHandleResult = handler.doHandle(wxMsg); checkResult(doHandleResult); Result afterHandleResult = handler.afterHandle(wxMsg); checkResult(afterHandleResult); result.status = true ; result.setProcessResult(doHandleResult.getData()); }cache(Exception e){ handler.throwHandle(wxMsg, e); throw e; }final { return result; } } private IWxMessageHandler selectHandler (WxMsg wxMsg) { List<IWxMessageHandler> typeOfHandlers = this .handlerMap.get(WxMsg.getMessageType); IWxMessageHandler result = null ; if (CollectionUtils.isNotEmpty(typeOfHandlers)){ for (IWxMessageHandler handler : typeOfHandlers){ if (handler.canHandle(wxMsg)){ result = handler; break ; } } } return result; } private void checkResult (Result result) { if (!ResultUtil.checkResult(result)){ throw new ResultErrorException (result); } } }
Q & A
Q:WxMessageProcessor
类也没有提供抽象方法和子类实现啊,这还是模板方法模式的实现吗?
A:模板方法模式
的核心在于封装算法的骨架,将算法的部分步骤下沉到子类实现。此处WxMessageProcessor
虽然不是抽象类,但是其processMessage
方法封装了消息处理的算法骨架,而具体的算法则由抽象工厂模式
来提供。所以,这里WxMessageProcessor
并不违背模板方法模式
的初衷。
Q:代码怎么运行不起来?
A:抱歉,本文的所有代码我都是在VSCode中手敲的,在编辑的时候,除了有一点代码高亮之外,并没有其他提示。而且本篇的Demo需求是真实的需求,但是代码并不是真实业务代码,仅为举例用。仅提供一个思路而已。
Q:WxMessageProcessor
中消息处理器是通过引入Spring IoC容器,并通过IoC容器进行获取的,这样代码就与Spring核心组件耦合了,某种意义上说违背了Spring设计的初衷,能否换一种实现?
A:因为本篇讲解的是模板方法模式
和抽象工厂模式
耦合,在代码的实现方式上有多种,核心思想是使用以上两种设计模式。至于SpringIoC的核心组件被耦合在代码中,换一个思路即可解决这个问题。目前WxMessageProcessor
获取消息处理器的方式是向Spring IoC容器主动获取,对于处理器本身来说是被动被引入,而WxMessageProcessor
需要在初始化的时候建立处理器的缓存,并对处理器列表进行相应的处理。换一个角度,可以在处理器初始化的时候,将其本身注册到WxMessageProcessor
中,这样WxMessageProcessor
的实例化过程就变得轻量了许多。但是另一个问题又来了,处理器耦合了WxMessageProcessor
,本身对于处理器来说WxMessageProcessor
对他们是不可见的,处理器不知道究竟是谁调用了它,某种意义上也造成了代码耦合。因为处理器是可以无限扩展的,最好的做法是单纯只扩展处理器,而无需让处理器知道谁调用它或者谁使用他,这样处理器才可以更灵活。当然,如果改用处理器向WxMessageProcessor
注册自己的方式,就又引入了观察者模式
。模板方法模式
、观察者模式
和抽象工厂模式
3P(淫笑.jpg)
如有更多问题或意见,欢迎大家在下方留言!