当事务的流程和步骤是固定好的,但是每一个步骤的具体实现方式是不一定的。这个时候就可以使用模板模式。
模版模式惯常的用法是,在一个方法模版方法中定义一个算法或者逻辑的流程和步骤,比如先调内部的方法A 再调内部方法B,满足某个条件了不调方法 C 等等,而这个流程中每个步骤对应的方法都可以推迟到子类中去实现,这就给了程序在不改变大流程、步骤的情况下,完成相似性业务的能力。
银行柜台办理业务,存款、取款、购买理财等这些业务的流程中都会有:取号、排位等号、处理业务、服务评价这几个步骤,如果你是金葵花之类的VIP用户,有可能有专属窗口不用排队,检查用户是不是VIP这样步骤叫做钩子方法。
type BankBusinessHandler interface {
// 排队拿号
TakeRowNumber()
// 等位
WaitInHead()
// 处理具体业务
HandleBusiness()
// 对服务作出评价
Commentate()
// 钩子方法,
// 用于在流程里判断是不是VIP, 实现类似VIP不用等的需求
checkVipIdentity() bool
}
Go 不支持抽象类和子类继承,通过类型匿名嵌套来实现,由一个外层类型包装组合BankBusinessHandler
接口的实现达到与抽象类和子类继承类似的效果。
type BankBusinessExecutor struct {
handler BankBusinessHandler
}
// 模板方法,处理银行业务
func (b *BankBusinessExecutor) ExecuteBankBusiness () {
// 适用于与客户端单次交互的流程
// 如果需要与客户端多次交互才能完成整个流程,
// 每次交互的操作去调对应模板里定义的方法就好,并不需要一个调用所有方法的模板方法
b.handler.TakeRowNumber()
if !b.handler.CheckVipIdentity() {
b.handler.WaitInHead()
}
b.handler.HandleBusiness()
b.handler.Commentate()
}
下面用模板模式实现一下存款业务的流程,代码如下:
type DepositBusinessHandler struct {
*DefaultBusinessHandler
userVip bool
}
// 通用的方法还可以抽象到BaseBusinessHandler里,组合到具体实现类里,减少重复代码(实现类似子类继承抽象类的效果)
func (*DepositBusinessHandler) TakeRowNumber() {
fmt.Println("请拿好您的取件码:" + strconv.Itoa(rand.Intn(100)) +
" ,注意排队情况,过号后顺延三个安排")
}
func (dh *DepositBusinessHandler) WaitInHead() {
fmt.Println("排队等号中...")
time.Sleep(5 * time.Second)
fmt.Println("请去窗口xxx...")
}
func (*DepositBusinessHandler) HandleBusiness() {
fmt.Println("账户存储很多万人民币...")
}
func (dh *DepositBusinessHandler) CheckVipIdentity() bool {
return dh.userVip
}
func (*DepositBusinessHandler) Commentate() {
fmt.Println("请对我的服务作出评价,满意请按0,满意请按0,(~ ̄▽ ̄)~")
}
执行存款业务的流程则由外部包装类定义的统一模板方法负责发起和调用每个步骤。
func NewBankBusinessExecutor(businessHandler BankBusinessHandler) *BankBusinessExecutor {
return &BankBusinessExecutor {handler: businessHandler}
}
func main() {
dh := &DepositBusinessHandler{userVip: false}
bbe := NewBankBusinessExecutor(dh)
bbe.ExecuteBankBusiness()
}
像排队取号,等位、服务评价这几个方法,各个银行业务的实现都一样。所以就可以把它们放在抽象类中可以进一步减少代码的重复率。
但是 Go 不是完全面向对象的语言,可以用类型的匿名嵌套组合来实现相似的效果,把这几个操作的方法交给DefaultBusinessHandler
类型实现,再由具体实现类组合它,同样能达到减少重复实现相同逻辑的效果。
type DefaultBusinessHandler struct {
}
func (*DefaultBusinessHandler) TakeRowNumber() {
fmt.Println("请拿好您的取件码:" + strconv.Itoa(rand.Intn(100)) +
" ,注意排队情况,过号后顺延三个安排")
}
func (dbh *DefaultBusinessHandler) WaitInHead() {
fmt.Println("排队等号中...")
time.Sleep(5 * time.Second)
fmt.Println("请去窗口xxx...")
}
func (*DefaultBusinessHandler) Commentate() {
fmt.Println("请对我的服务作出评价,满意请按0,满意请按0,(~ ̄▽ ̄)~")
}
func (*DefaultBusinessHandler) CheckVipIdentity() bool {
// 留给具体实现类实现
return false
}
func NewBankBusinessExecutor(businessHandler BankBusinessHandler) *BankBusinessExecutor {
return &BankBusinessExecutor {handler: businessHandler}
}
在互联网里C端产品里的典型应用场景,比如:用户经营类的活动API,所有活动都可以抽象成:展示活动信息、奖品信息、判断用户资格、参与活动、抽奖、查看中奖记录、核销奖品这些步骤。可以利用模板设计模式来对业务流程做抽象,实现各种用户活动都能用一套统一的RESTful API 来支撑业务的效果。
在实际开发中,从来没有哪个设计模式是可以独立应用的,更多的时候是几个设计模式联合使用,群策群力、相辅相承来达到项目设计的效果。模板模式适合与简单工厂配合使用
模板模式缺点:如果提炼得不到位,就得频繁增加或者修改流程里的步骤–也就是修改表示流程的 interface 或者抽象类里的方法。这个时候,如果现有业务中已经存在了多个该流程的实现类的话,那么它们都得做出相应调整才行。
参考公众号网管叨bi叨