Skip to content

装饰模式

概念

  • 一种动态的往一个类中添加新的行为的设计模式
  • 就功能而言,修饰模式相比生成子类更为灵活,这样可以给某个对象而不是整个类添加一些功能
  • 它是一种对象结构型模式

结构

Image title

装饰模式结构图

代码示例

  • decorate.go

    package decorate
    
    import "fmt"
    
    // 定义一个抽象组件
    type Component interface {
        Operate()
    }
    
    // 实现一个具体的组件:组件1
    type Component1 struct {
    }
    
    func (c1 *Component1) Operate() {
        fmt.Println("c1 operate")
    }
    
    // 定义一个抽象装饰者
    type Decorate interface {
        Component
        Do() // 这是个额外的方法
    }
    
    // 实现一个具体的装饰者
    type Decorate1 struct {
        c Component
    }
    
    func (d1 *Decorate1) Do() {
        fmt.Println("发生了装饰行为")
    }
    
    func (d1 *Decorate1) Operate() {
        d1.Do()
        d1.c.Operate()
    }
    

  • decorate_test.go

    package decorate
    
    import "testing"
    
    func TestComponent1_Operate(t *testing.T) {
        c1 := &Component1{}
        c1.Operate()
    
        /*
            === RUN   TestComponent1_Operate
            c1 operate
            --- PASS: TestComponent1_Operate (0.00s)
            PASS
    
            Process finished with the exit code 0
        */
    }
    
    func TestDecorate1_Operate(t *testing.T) {
        d1 := &Decorate1{}
        c1 := &Component1{}
        d1.c = c1
        d1.Operate()
    
        /*
            === RUN   TestDecorate1_Operate
            发生了装饰行为
            c1 operate
            --- PASS: TestDecorate1_Operate (0.00s)
            PASS
    
            Process finished with the exit code 0
        */
    }
    

实战模拟

  • 实现http中间件记录请求的URL、方法
  • 实现http中间件记录请求的网络地址
  • 实现http中间件记录请求的耗时
    package main
    
    import (
        "fmt"
        "log"
        "net/http"
        "time"
    )
    
    // 1. 实现一个 http server
    // 2. 实现一个 handler:hello
    // 3. 实现中间件的功能  1.记录请求URL和请求类型  2.记录请求的网络的地址  3.记录方法的执行时间
    func tracing(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            log.Printf("tracing start 记录请求的网络地址:%s", r.RemoteAddr)
            next.ServeHTTP(w, r)
            log.Println("tracing end")
        })
    }
    
    func logging(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            log.Printf("logging start 记录请求的网络地址:%s", r.RemoteAddr)
            next.ServeHTTP(w, r)
            log.Println("logging end")
        })
    }
    
    func timeRecording(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            log.Println("time Record start")
            startTime := time.Now()
            next.ServeHTTP(w, r)
            lastTime := time.Since(startTime)
            log.Printf("记录请求的耗时:%s", lastTime)
            log.Println("time Record end")
        })
    }
    
    func hello(w http.ResponseWriter, r *http.Request) {
        //log.Printf("记录请求的网络地址:%s", r.RemoteAddr)
        //log.Printf("记录请求的网络地址:%s", r.RemoteAddr)
        //startTime := time.Now()
        fmt.Fprintf(w, "hello")
        //lastTime := time.Since(startTime)
        //log.Printf("记录请求的耗时:%s", lastTime)
    }
    
    func main() {
        http.Handle("/", tracing(logging(timeRecording(http.HandlerFunc(hello)))))
        http.ListenAndServe("localhost:8080", nil)
    }
    
    
    
    // 执行结果
    /private/var/folders/7w/0j7vff4j1kz5_g43cf_vqlg00000gp/T/GoLand/___go_build_designpattern_httpmiddleware
    2023/02/27 14:35:59 tracing start 记录请求的网络地址127.0.0.1:59347
    2023/02/27 14:35:59 logging start 记录请求的网络地址127.0.0.1:59347
    2023/02/27 14:35:59 time Record start
    2023/02/27 14:35:59 记录请求的耗时32.877µs
    2023/02/27 14:35:59 time Record end
    2023/02/27 14:35:59 logging end
    2023/02/27 14:35:59 tracing end
    2023/02/27 14:35:59 tracing start 记录请求的网络地址127.0.0.1:59347
    2023/02/27 14:35:59 logging start 记录请求的网络地址127.0.0.1:59347
    2023/02/27 14:35:59 time Record start
    2023/02/27 14:35:59 记录请求的耗时2.526µs
    2023/02/27 14:35:59 time Record end
    2023/02/27 14:35:59 logging end
    2023/02/27 14:35:59 tracing end
    

装饰模式总结

  • 优点:
    1. 可以通过一种动态的方式来扩展一个对象的功能
    2. 可以使用多个具体装饰类来装饰同一对象,增加其功能
    3. 具体组件类与其他装饰类可以独立变化,符合“开闭原则”
  • 缺点:
    1. 对于多次装饰的对象,易于出错,排错也困难
    2. 对于产生很多具体装饰类,增加系统的复杂度以及理解成本
  • 适合场景:
    1. 需要给一个对象增加功能,这些功能可以动态的撤销
    2. 需要给一批兄弟类增加或者改装功能