当模板方法模式遇上抽象工厂模式

发布 : 2019-12-09 分类 : 技术 浏览 :

模板方法模式定义一个操作中的算法骨架,而将一些步骤延迟到子类中,模板方法使得子类可以不改变一个算法的结构即可重新定义该算法的某些特定步骤。抽象工厂模式是一种为调用者(客户端)提供一个创建一组相关或相互依赖的对象的接口,且调用者无需指定所要产品的具体类就能得到同族不同等级的产品的模式结构。而这两种设计模式在一起使用,彼此交融,将会碰撞出什么样的火花呢?先回顾一下这两个设计模式吧

设计模式回顾

模板方法模式

模板方法模式
定义一个操作中的算法骨架,而将一些步骤延迟到子类中,模板方法使得子类可以不改变一个算法的结构即可重新定义该算法的某些特定步骤。

在不使用设计模式时,一个算法的实现中,因算法步骤中部分逻辑变更,可能会因只改变某几行代码逻辑,而复制整合算法结构的代码。这样会造成大量重复代码,不易维护。而使用模板方法模式,则只需创建一个调用器(invoker)并向外暴露一个调用器方法(invoke),将算法的各个步骤封装到单独的方法;将算法的骨架(执行顺序、执行逻辑)封装在调用器方法中即可。因调用器不能完全了解每个步骤的实现,算法的部分步骤可以在该类中提供默认的实现,让子类选择性覆盖;而某些步骤必须让子类实现才可以完成,则对应步骤的方法就应该设置为抽象(称之为:钩子函数),对应的类也应该设置为抽象。

类图

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()
    }
模板方法类图

CustomInvokerACustomInvokerB继承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
public abstract class Invoker{

public final void invoke(){
customFunctionA();
customFunctionB();
}

/**
* 父类提供默认实现,子类选择性覆盖
*/
protected void customFunctionA(){
// parent do something;
}

/**
* 父类无法确定具体实现,子类必须覆盖此部分逻辑
*/
protected abstract void customFunctionB();
}

public class CustomInvokerA{
@Override
public void customFunctionB(){
// subClass do something;
}
}

public class CustomInvokerA{

@Override
public void customFunctionA(){
// subClass something;
}

@Override
public void customFunctionB(){
// subClass something;
}
}

public class Client{
private Invoker invoker;

public Client(Invoker invoker){
this.invoker = invoker;
}

public void invoke(){
this.invoker.invoke();
}
}

抽象工厂模式

抽象工厂模式
为调用者(客户端)提供一个创建一组相关或相互依赖的对象的接口,且调用者无需指定所需产品的具体类就能得到同族不同等级的产品的模式结构。

抽象工厂模式可以说是工程方法模式的升级版,虽然工程方法模式的工厂类可以提供多个工厂方法,但通过多个工厂方法获取的多个对象之间并无联系,或者说获取的这些对象之间彼此无依赖。而抽象工厂模式的工厂类中提供的工厂方法所生产的产品都具有相关性或彼此依赖,其提供的是一套产品生产的流水线,客户端只需切换产品流水线,即可切换整个产品族。抽象工厂模式中有这么几个概念:

  1. 抽象工厂(Abstract Factory) : 提供产品线生产产品的抽象描述
  2. 具体工厂(Concrete Factory) : 抽象工厂的具体实现,负责产品线上每个产品的具体实现
  3. 抽象产品(Abstract Product) : 产品的抽象描述
  4. 具体产品(Concrete Product) : 抽象产品的具体实现

类图

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
/**
* 客户端
*/
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();
}

/**
* 具体工厂A
*/
public class ConcreteFactoryA extends AbstractFactory{
public AbstractProductA getProductA(){
return new ConcreteProductA1();
}
public AbstractProductB getProductB(){
return new ConcreteProductB1();
}
}

/**
* 具体工厂B
*/
public class ConcreteFactoryB extends AbstractFactory{
public AbstractProductA getProductA(){
return new ConcreteProductA2();
}
public AbstractProductB getProductB(){
return new ConcreteProductB2();
}
}

/**
* 抽象产品A
*/
public abstract class AbstractProductA{
public AbstractProductA(){}
}

/**
* 具体产品A1
*/
public class ConcreteProductA1 extends AbstractProductA{
public ConcreteProductA1(){}
}

/**
* 具体产品A2
*/
public class ConcreteProductA2 extends AbstractProductA{
public ConcreteProductA2(){}
}

/**
* 抽象产品A
*/
public abstract class AbstractProductB{
public AbstractProductB(){}
}

/**
* 具体产品B1
*/
public class ConcreteProductB1 extends AbstractProductB{
public ConcreteProductB1(){}
}

/**
* 具体产品B2
*/
public class ConcreteProductB2 extends AbstractProductB{
public ConcreteProductB2(){}
}

组合思路

模板方法模式封装算法,对算法特定步骤下沉到子类实现;而抽象工厂模式可以提供同族不同级别的产品。如果对模板方法模式的所有算法都包装成对象,提供给调用器方法,如果想要变更算法,就需要切换一组算法对象,而切换一组相互关联的对象,就可以使用抽象工厂模式来实现。回过头来看,模板方法模式用来封装算法,而抽象工厂模式用来提供算法某些步骤的实现,这是个好主意。(Perfect!)

组合类图及分析

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发送给用户,此时用户端的聊天界面会以微信模板消息形式展示学分信息。

流程图

下图按系统内外部对整个消息处理过程简单描述

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,表示该消息服务端已经正确处理。否则,微信会按照文档约定的间隔时间和次数重发消息。

类图

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)
    }
  • WechatApiQualityApi分别是对微信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接口,是消息处理器的抽象实现,提供了公共方法和部分处理器方法的默认实现,子类可选择性覆盖。
  • BaseWxTextMessageHandlerBaseWxImageMessageHandlerBaseWxVideoMessageHandler都继承了BaseWxMessageHandler,是特定微信消息类型的消息处理器抽象实现。例如:BaseWxTextMessageHandler用于处理文本消息,BaseWxImageMessageHandler用于处理图片消息,BaseWxVideoMessageHandler用于处理视频消息等,具体可以参照微信官方文档消息管理中提供的已知消息类型。
    • final String getCanHandleMsgType()方法:该方法制定了可处理该类型消息的处理器本身所具备的特点,就是只负责处理本类型微信消息
  • QualityCreditsQueryWxTextMessageHandlerPracticalCreditsQueryWxTextMessageHandler分别是素质学分查询实践学分查询消息的处理器具体实现。在该处理器实现中,调用了QualityApi相应的查询接口获取业务查询结果。

时序图

系统启动加载时序图

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实例,即获取所有消息处理器对象,并对消息处理器进行分组。 #### 消息处理时序图
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表示消息处理成功

代码实现

因篇幅原因,对WxMsgControllerWechatApiQualityApi的代码不做描述。

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;

/**
* 处理并响应微信消息
* @param message 加密微信消息内容
*/
@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";
}

/**
* 发送微信消息
* @param wxMsg 微信消息实体
*/
private void sendWxMsg(WxMsg wxMsg){
this.wechatApi.sendWxMsg(wxMsg);
}
}

IWxMessageHandler消息处理器抽象接口

1
2
3
4
5
6
7
8
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
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;
}
}

BaseWxTextMessageHandlerBaseWxImageMessageHandlerBaseWxVideoMessageHandler等,针对微信消息的类型,提供不同的消息类型处理器抽象。如果要对新的消息类型进行处理扩展,则可以继承BaseWxMessageHandler类。每个消息类型处理器都包含了一组本类型的处理器集合,例如BaseWxTextMessageHandler处理器的所有子类,默认处理文本消息类型的微信消息,可以选择根据消息内容进行消息处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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";
}
}

QualityCreditsQueryWxTextMessageHandlerPracticalCreditsQueryWxTextMessageHandler都继承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
@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依赖项注入之后,立即调用初始化生命周期方法。该方法被调用时,若消息处理器缓存为空,则加载消息处理器,通过ApplicationContextgetBeansOfType方法获取所有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(); // status默认为false
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)

如有更多问题或意见,欢迎大家在下方留言!

本文作者 : 陈志昊
原文链接 : https://blog.chenzhihao.cc/2019/12/09/当模板方法模式遇上抽象工厂模式/
版权声明 : 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明出处!
留下足迹