深圳幻海软件技术有限公司 欢迎您!

学会用这个设计模式思考业务抓手,OKR绩效想不拿优都难

2023-02-28

大家好,我是网管,今天又上来给大家更新设计模式系列的文章啦,之前已经把四种建造型的设计模式更新齐全啦,没有看过的小伙伴可以通过点击上面和文章尾部的系列合集链接,进行查看。在开始讲今天的设计模式之前我先问个问题:“你们公司现状有没有用OKR管理季度或者双月的个人目标、团队目标吗?”现在越来越多公司开始

大家好,我是网管,今天又上来给大家更新设计模式系列的文章啦,之前已经把四种建造型的设计模式更新齐全啦,没有看过的小伙伴可以通过点击上面和文章尾部的系列合集链接,进行查看。

在开始讲今天的设计模式之前我先问个问题:“你们公司现状有没有用 OKR 管理季度或者双月的个人目标、团队目标吗?” 现在越来越多公司开始用 OKR 代替 KPI 做目标管理,两套目标管理系统的详细区别我就不说了,因为我也说不清楚。。。哈哈!

我们可以简单粗暴的理解为 OKR 是自底向上的制定目标,而 KPI 是上头大老板直接给你拍个目标。但实际施行起来,可能,我是说可能哈,最关键的跟公司赚钱活下去的业务目标还是上头老板直接拍给你,只留下一些类似自己/团队/公司 积累、进步、成长这些不疼不痒的目标是让员工自我决定,自底向上完成目标制定。

对于做研发的技术人员来说,个人、团队、公司上的成长无非就是个人的技术再精进些、系统再优化优化、给公司技术积累增加点方法论实践什么的,但是呢,你写目标的时候不能写的太 Low 了,比如 “重构什么什么流程、提高XXX” 大土话直接拽上去可不行,OKR 讲究把话术说的让别人看了就兴奋,让别人觉得:“我早就想到了一直等你们提出来细化啦”。。。这个别人就是你的直属领导,因为你的OKR大概率只有它看,更高层的不太会看你的 OKR 。

有的人可能会问了,我来你这学设计模式呢,你跟我说半天 OKR 目标管理干啥?这你就不懂了,如果你能把流程重构这种看起来平平无奇的话细化成运用XXX设计模式重构XX流程,增加其无限扩展业务逻辑的能力、结合 XXX 设计模式减少XX业务重复开发率提升人效XXX,这样给人的感觉是不是完全不一样了,那这个设计模式是不是真的能达到你说的效果,如果你领导好几年不写代码了,大概率它是不知道的,但是这些个设计模式在行业里很有名,但凡知道有设计模式这个东西的,都会觉得你这个目标定的很有水平。

当然我这里不是在教大家长袖善舞,而是怎么把一些理论的东西结合实践表达出来,像 UML、GoF 23 种设计模式、DDD里的某些模式,这些在行业内经过长时间反复验证行的通的方法论,拿出来就是会让人觉得很专业、靠谱。

那能表达出来的前提是我们得会,真的得用心思考什么设计模式才能解决当下系统的痛点,接下来就进入咱们学习设计模式的正题。

那在这里我先抛出一个我自己总结的暴论:“模版、策略和职责链三个设计模式是解决业务系统流程复杂多变这个痛点的利器”,你可以延伸一下进一步理解成,这三个设计模式也是你在公司里卷 OKR 的利器。当然设计模式里并不是只有这三个是利器,只是这三个最基础,会了之后思考问题马上会不一样,等完全掌握了再跟更复杂的设计模式结合起来使用也不迟,

今天这里给大家先来介绍模版模式,因为策略有些时候步骤里会应用上模版模式,我们就放到下一篇文章再分享。我们先来看下模版模式长什么样,使用起来代码该怎么写,最后再给大家分析用模版模式怎么分析系统现在的问题、或者更进一步你下个季度的 OKR 该怎么定。

什么是模板模式

模版模式,有的也翻译成模版方法模式,主要是因为这个模式里有个模版方法,不过后面实际应用的时候我会提到,这个模版方法在设计一些有客户端和服务多次交互的场景里,其实也可以是虚拟的,我们自己形成意识设计API即可,不一定非要在设计模式的类实现里真实存在。

当要做一件事儿的时候,这件事儿的流程和步骤是固定好的,但是每一个步骤的具体实现方式是不一定的。这个时候就可以使用模板模式。

模版模式惯常的用法是,在一个方法模版方法中定义一个算法或者逻辑的流程和步骤,比如先调内部的方法A 再调内部方法B,满足某个条件了不调方法 C 等等,而这个流程中每个步骤对应的方法都可以推迟到子类中去实现,这就给了程序在不改变大流程、步骤的情况下,完成相似性业务的能力。

模版模式实现起来非常简单,用抽象类定义好步骤,提供步骤的默认实现,具体业务逻辑上每个步骤的实现差异交给子类去实现就可以。模版模式的结构用 UML 类图可以这么表示

下面举一个我们都见过的业务流程的例子,结合代码实现让大家更好地体会下模版模式怎么使用,如果是 Java 来实现模版模式的话真的是非常简单,直接用抽线类和子类实现就完事了,网上资料有很多我就不多说,下面我用 Go 代码实现一下模版设计模式,主要是因为 Go 不支持继承,但是又有类型匿名嵌套实现差不多继承的效果,所以代码写起来会绕点弯。

模板模式用法举例

比如我们去银行柜台办理业务,存款、取款、购买理财等这些业务的流程中都会有:取号、排位等号、处理业务、服务评价这几个步骤,如果你是金葵花之类的VIP用户,有可能有专属窗口不用排队,检查用户是不是VIP这样步骤叫做钩子方法。

// 本文使用的完整可运行源码
// 去公众号「网管叨bi叨」发送【设计模式】即可领取
type BankBusinessHandler interface {
 // 排队拿号
 TakeRowNumber()
 // 等位
 WaitInHead()
 // 处理具体业务
 HandleBusiness()
 // 对服务作出评价
 Commentate()
 // 钩子方法,
    // 用于在流程里判断是不是VIP, 实现类似VIP不用等的需求
 checkVipIdentity() bool
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.

模板方法,由于 Go 不支持抽象类和子类继承,我们通过类型匿名嵌套来实现,由一个外层类型包装组合BankBusinessHandler接口的实现达到与抽象类和子类继承类似的效果。

// 本文使用的完整可运行源码
// 去公众号「网管叨bi叨」发送【设计模式】即可领取
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()
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.

模版模式里:存款、取款与银行客户业务这三者的关系,可以用下面的 UML 图清晰地展示出来:

接下来我们就可以在子类里实现每个银行客户业务的逻辑啦,但是不管哪个业务,都脱离不了取号、等位、办业务、评价服务的大流程。

下面用模板模式实现一下存款业务的流程,代码如下:

"完整可运行源码,私信给
公众号「网管叨bi叨」发送【设计模式】即可领取"
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,(~ ̄▽ ̄)~")
}
  • 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.

执行存款业务的流程则由外部包装类定义的统一模板方法负责发起和调用每个步骤。

func NewBankBusinessExecutor(businessHandler BankBusinessHandler) *BankBusinessExecutor {
 return &BankBusinessExecutor {handler: businessHandler}
}

func main()  {
 dh := &DepositBusinessHandler{userVip: false}
 bbe := NewBankBusinessExecutor(dh)
 bbe.ExecuteBankBusiness()
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

上面实现存款业务流程的时候,我们会发现,像排队取号,等位、服务评价这几个方法,各个银行业务的实现都一样。 所以就可以把它们放在抽象类中可以进一步减少代码的重复率。

但是 Go 不是完全面向对象的语言,不过我们可以用类型的匿名嵌套组合来实现相似的效果,把这几个操作的方法交给DefaultBusinessHandler类型实现,再由具体实现类组合它,同样能达到减少重复实现相同逻辑的效果。

"完整可运行源码,私信给
公众号「网管叨bi叨」发送【设计模式】即可领取"
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}
}
  • 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.

注意,上面的DefaultBusinessHandler并没有实现我们想要留给具体子类实现的HandleBusiness方法,这样 DefaultBusinessHandler 就不能算是BankBusinessHandler接口的实现,这么做是为了这个类型只能用于被实现类包装,让 Go 语言的类型检查能够帮我们强制要求,必须用存款或者取款这样子类去实现HandleBusiness方法,整个银行办理业务的流程的程序才能运行起来。

本文的完整源码,已经同步收录到我整理的电子教程里啦,可向我的公众号「网管叨bi叨」发送关键字【设计模式】领取。

模板模式的使用建议不一定非要有模版方法

这里,我们例子里这种定义模板方法的方式适用于与客户端单次交互的流程

type BankBusinessExecutor struct {
 BankBusinessHandler
}

// 模板方法,处理银行业务
func (b *BankBusinessExecutor) ExecuteBankBusiness () {
 b.TakeRowNumber()
 if !b.CheckVipIdentity() {// VIP 不用等位
  b.WaitInHead()
 }
 b.HandleBusiness()
 b.Commentate()
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.

如果需要与客户端多次交互才能完成整个流程,可以每个交互的操作去使用模板里定义的方法,这个时候,并不需要定义一个调用所有方法的模板方法,这种情况下,也可以理解成,整个流程用到的 RESTful API 接口组合扮演的就是模板方法的角色。

在互联网里C端产品里的典型应用场景,比如:用户经营类的活动API,所有活动都可以抽象成:展示活动信息、奖品信息、判断用户资格、参与活动、抽奖、查看中奖记录、核销奖品这些步骤。那么我们可以利用模板设计模式来对业务流程做抽象,实现各种用户活动都能用一套统一的RESTful API 来支撑业务的效果。

模版与工厂结合使用

还有这里再说一点,在实际开发中,从来没有哪个设计模式是可以独立应用的,更多的时候是几个设计模式联合使用,群策群力、相辅相承来达到项目设计的效果。

而由模版模式把流程的实现逻辑推迟到子类,我们大概也能想到,创建模版子类这个工作交给工厂模式是再合适不过的了,具体使用哪种工厂?一般简单工厂就好,项目刚开始的时候,一般情况下,业务需求和流程我们挖掘的还不够全面,所以一开始的时候不要做太深度的提炼和抽象,等到确实需要的时候再升级到抽象工厂也未尝不可。

模版模式的优缺点

模板方法模式的优点

利用模板方法将相同处理逻辑的代码放到抽象父类中,可以提高代码的复用性。

将不同的算法逻辑分离到不同的子类中,通过对子类的扩展增加新的行为,提高代码的可扩展性。

把不变的行为写在父类上,去除子类的重复代码,提供了一个很好的代码复用平台,符合开闭原则。

模板方法模式的缺点

  • 由于继承关系自身的缺点,如果父类添加新的抽象方法,则所有子类都要改一遍。

模版模式这么好,那我们是不是所有流程都要应用上呢?肯定不是,它更适合于经过我们大量实践后,能把某个核心流程提炼成固定步骤的时候再应用。如果提炼的不到位,就得频繁增加或者修改流程里的步骤--也就是修改表示流程的 interface 或者抽象类里的方法。这个时候,如果现有业务中已经存在了多个该流程的实现类的话,那么它们都得做出相应调整才行。

模版模式与 OKR 的结合

学完模版模式后我们再来说说怎么用这个设计模式的思想为指导,让你发现现有业务系统中需要改进的流程。 互联网公司,某项业务刚开始,可以简单粗暴,先上线,即使早期该业务相似流程多,代码重复率高也没事,等到跑个一段时间,这个业务流程确定要长期做下去,我们对其的理解和抽象提炼也到位后,就可以主动思考怎么通过模版模式对流程进行优化啦。

就像上面举的那个很经典的例子,在大部分互联网公司的产品生态里都会有一些促活、拉新、提升客单价的用户活动,实现形式往往就是种树、福袋开奖、转盘开奖之类的,早期业务发展期因为这类活动形态有很多,程序员并不知道产品过两天又回规划出什么活动内容来,所以一般都是一个活动一组API、一组类。

那么这个时候你就可以尝试用模版模式的思维模式,思考找出这些相似业务具有通用步骤,是不是把每个同类业务往这套模版上的步骤里套都能实现,如果不行的话,再继续思考和提炼。

假设你在的公司正好是以 OKR 做续期管理的,那么你就可以把提炼核心流程、减少代码重复开发量、提高相似业务上线效率这些方面作为目标,把他们写到OKR里,这个过程中既提高了我们技术上的流程抽象设计能力,也向上管理了组织对我们的预期,是不是不失为一个向上管理的好方法呀。