设计模式简介

设计模式一套被反复使用,多数人知晓的代码设计经验的总结,实现可重用代码,使代码更容易被理解,保证代码可靠性。

总体来说,设计模式分为三大类:

  • 创建型模式(五种):单例模式、工厂模式、抽象工厂模式、创建者模式、原型模式
  • 结构型模式(七种):适配器模式、代理模式、装饰器模式、外观模式、桥接模式、组合模式、享元模式
  • 行为型模式(十一种):策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

算法执行的步骤是稳定不变的,但是具体的某些算法可能存在变化的场景。

怎么理解,举个例子:比如说你煮个面,必然需要先烧水,水烧开之后再放面进去,以上的流程我们称之为煮面过程。可知:这个煮面过程的步骤是稳定不变的,但是在不同的环境烧水的方式可能不尽相同,也许有的人用天然气烧水、有的人用电磁炉烧水、有的人用柴火烧水,等等。我们可以得到以下结论:

  • 煮面过程的步骤是稳定不变的
  • 煮面过程的烧水方式是可变的

常见的设计模式

  1. 单例模式(最简单的一种设计模式)

    使用懒惰模式的单例模式,使用双重检查加锁保证线程安全『对象仅实例化一次』

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    
    type CustomValidator struct {
        once     sync.Once
        validate *validator.Validate
    }
    func (c *CustomValidator) Validate(v interface{}) error {
        c.lazyInit()
        return c.validate.Struct(v)
    }
    func (c *CustomValidator) lazyInit() {
        c.once.Do(func() {
            c.validate = validator.New()
        })
    }
    
  2. 工厂模式 vs 抽象工厂模式

    定义

    • 工厂模式 定义一个用于创建对象的接口,让子类决定实例化哪一个类
    • 抽象工厂模式 为创建一组相关或相互依赖的对象提供一个接口,而且无需指定他们的具体类

    区别

    工厂模式针对的是一个产品等级结构 ,抽象工厂模式针对的是面向多个产品等级结构的。『示例:在每一个层次,种菜工人所关心的对象也不一样,在简单工厂模式下,工人要想到种植萝卜还是白菜,在工厂模式下,工人想到是种植根菜还是茎菜,而在抽象工厂模式下,则关心种植基因菜还是非基因菜。』

    工厂模式和抽象工厂模式的区别

    示例1

    示例2)

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    
    //Operator 是被封装的实际类接口
    type Operator interface {
        SetA(int)
        Result() int
    }
    
    type OperatorB interface {
        SetB(int)
        Result() int
    }
    
    //OperatorFactory 采用了工厂模式
    type OperatorFactory interface {
        Create() Operator // 种菜
    }
    
    //OperatorFactory 采用了抽象工厂模式
    type OperatorFactory2 interface {
        CreateA() Operator  // 种根菜
        CreateB() OperatorB // 种芹菜
    }
    
  3. 创建者模式

    应用场景:一些基本组件不会变,而其组合经常变化的时候。『有相同的接口方法,但各自拥有特殊的接口方法』

     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
    
    type Builder interface {
        Part1()
        Part2()
    }
    
    type Director struct {
        builder Builder
    }
    func (d *Director) Construct() {
        d.builder.Part1()
        d.builder.Part2()
    }
    
    func NewDirector(builder Builder) *Director {
        return &Director{
            builder: builder,
        }
    }
    
    type Builder1 struct {
        result string
    }
    func (b *Builder1) Part1() {
        b.result += "1"
    }
    func (b *Builder1) Part2() {
        b.result += "2"
    }
    func (b *Builder1) GetResult() string { // Builder1 特殊方法
        return b.result
    }
    
    type Builder2 struct {
        result int
    }
    func (b *Builder2) Part1() {
        b.result += 1
    }
    func (b *Builder2) Part2() {
        b.result += 2
    }
    func (b *Builder2) GetResult2() int { // Builder2 特殊方法
        return b.result
    }
    
    // 测试用例
    func TestBuilder1(t *testing.T) {
        builder := &Builder1{}
        director := NewDirector(builder)
        director.Construct()
        res := builder.GetResult()
        if res != "12" {
            t.Fatalf("Builder1 fail expect 12 acture %s", res)
        }
    }
    func TestBuilder2(t *testing.T) {
        builder := &Builder2{}
        director := NewDirector(builder)
        director.Construct()
        res := builder.GetResult()
        if res != 3 {
            t.Fatalf("Builder2 fail expect 3 acture %d", res)
        }
    }
    
  4. 适配器模式 vs 装饰器模式

    适配器模式:抛弃旧接口逻辑,访问旧接口时直接调转到请求新接口

    意图:将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。 主要解决:主要解决在软件系统中,常常要将一些"现存的对象"放到新的环境中,而新环境要求的接口是现对象不能满足的。

    优点: 1、可以让任何两个没有关联的类一起运行。 2、提高了类的复用。 3、增加了类的透明度。 4、灵活性好。

    缺点: 过多地使用适配器,会让系统非常零乱,不易整体进行把握。比如,明明看到调用的是 A 接口,其实内部被适配成了 B 接口的实现,一个系统如果太多出现这种情况,无异于一场灾难。因此如果不是很有必要,可以不使用适配器,而是直接对系统进行重构。

    使用场景:有动机地修改一个正常运行的系统的接口,这时应该考虑使用适配器模式。

    装饰器模式:继承旧接口逻辑,新接口获取旧接口返回值后,增加新的补充逻辑

    装饰模式使用对象组合的方式动态改变或增加对象行为。

  5. 代理模式

    代理模式用于延迟处理操作或者在进行实际操作前后进行其它处理。

    实际案例:如控制器层调用业务层的方法前,可以做一些参数验证(如中间件)等,调用业务层的方法后,再做一些数据封装等,最终输出结果。整理流程来看,控制器层的用法就是代理模式。

  6. 模板模式

    抽象类里定义好算法的执行步骤和具体算法,以及可能发生变化的算法定义为抽象方法。不同的子类继承该抽象类,并实现父类的抽象方法。

    模板模式的优势:

    • 不变的算法被继承复用:不变的部分高度封装、复用。
    • 变化的算法子类继承并具体实现:变化的部分子类只需要具体实现抽象的部分即可,方便扩展,且可无限扩展。
  7. 职责链模式 vs 组合模式

    职责链模式

    首先把一系列业务按职责划分成不同的对象,接着把这一系列对象构成一个链,然后在这一系列对象中传递请求对象,直到被处理为止。

    具体优势:

    • 直观:一眼可观的业务调用过程
    • 无限扩展:可无限扩展的业务逻辑
    • 高度封装:复杂业务代码依然高度封装
    • 极易被修改:复杂业务代码下修改代码只需要专注对应的业务类(结构体)文件即可,以及极易被调整的业务执行顺序

    「职责链模式」 与「组合模式」的区别:

    • 职责链模式: 链表
    • 组合模式:树
     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
    
    // 职责链模式-商城购物下单业务示例
    // Context Context
    type Context struct {
    }
    
    // Handler 处理
    type Handler interface {
        // 自身的业务
        Do(c *Context) error
        // 设置下一个对象
        SetNext(h Handler) Handler
        // 递归执行
        Run(c *Context) error
    }
    
    // Next 抽象出来的 可被合成复用的结构体
    type Next struct {
        // 下一个对象
        nextHandler Handler
    }
    
    // SetNext 实现好的 可被复用的SetNext方法
    // 返回值是下一个对象 方便写成链式代码优雅
    // 例如 nullHandler.SetNext(argumentsHandler).SetNext(signHandler).SetNext(frequentHandler)
    func (n *Next) SetNext(h Handler) Handler {
        n.nextHandler = h
        return h
    }
    
    // Run 执行
    func (n *Next) Run(c *Context) (err error) {
        // 由于go无继承的概念 这里无法执行当前handler的Do
        // n.Do(c)
        if n.nextHandler != nil {
            // 合成复用下的变种
            // 执行下一个handler的Do
            if err = (n.nextHandler).Do(c); err != nil {
                return
            }
            // 执行下一个handler的Run
            return (n.nextHandler).Run(c)
        }
        return
    }
    
    // ArgumentsHandler 校验参数的handler
    type ArgumentsHandler struct {
        // 合成复用Next
        Next
    }
    
    // Do 校验参数的逻辑
    func (h *ArgumentsHandler) Do(c *Context) (err error) {
        fmt.Println(runFuncName(), "校验参数成功...")
        return
    }
    
    // AddressInfoHandler 地址信息handler
    type AddressInfoHandler struct {
        // 合成复用Next
        Next
    }
    
    // Do 校验参数的逻辑
    func (h *AddressInfoHandler) Do(c *Context) (err error) {
        fmt.Println(runFuncName(), "获取地址信息...")
        fmt.Println(runFuncName(), "地址信息校验...")
        return
    }
    
    func main() {
        handler := &Next{}
    
        // 链式调用
        handler.SetNext(&ArgumentsHandler{}).
            SetNext(&AddressInfoHandler{})
            //无限扩展代码...
    
        // 开始执行业务
        if err := handler.Run(&Context{}); err != nil {
            fmt.Println("Fail | Error:" + err.Error())
            return
        }
        fmt.Println("Success")
        return
    }
    
  8. 策略模式 vs 状态模式

    • 策略模式:依靠客户决策(如支付方式)
    • 状态模式:依靠内部状态决策(如后端发送短信商的选择)

工作案例

  1. Master-Worker模式

    其是常用的并行设计模式。核心思想是,系统由两个角色组成,Master和Worker,Master负责接收和分配任务,Worker负责处理子任务。任务处理过程中,Master还负责监督任务进展和Worker的健康状态;Master将接收Client提交的任务,并将任务的进展汇总反馈给Client

    图片

  2. 典型的客户端 - 服务端模式

参考文章