lzh2nix / articles

用 issue 来管理个人博客
https://github.com/lzh2nix/articles
62 stars 13 forks source link

装饰器模式 #127

Open lzh2nix opened 3 years ago

lzh2nix commented 3 years ago

所有的设计模式和架构设计都是解决软件规模变大之后的问题, 这也是看装饰器模式这一章之后的第一感觉。在小的项目中确实可以不用做过多的设计,一个简单的继承基本上都能搞定,比如这样 image 当项目变大变复杂之后会变成什么样?无限制的扩展吗? 像这样: image

所谓模式就是前人总结的经验,在工程上你目前遇到的问题,你肯定不是第一个遇到,所有寻找业界的做的是很关键的,可以看看那些成熟的项目是如何处理的。

这里与其叫装饰器模式,不如叫 wrap模式, 这样更形象一些,不断的在基础功能上套附加功能。这里的关键是提供方只提供了基础的被装饰类和装饰类(各个类之间又遵循一种原则),然后怎样装饰一个类完全交给使用方,这样可以避免在提供方进行层次的继承,这里也体现了设计中 组合优于继承 的原则。另外在新增被装饰类和装饰类时也不用修改老的代码,也符合 开闭原则

比如下面的类结构中: image 如果我们要计算““double mocha soy latte with whip” beverage” 的cost的时候就可以按照下面剥洋葱式的计算方式:

image

下面我们就用golang来实现上面的类结构:

type Beverage interface {
    Cost() float64
    Desc() string
}

//------------ 基本款(被装饰类)--------

type Espresso struct {
}

func (e *Espresso) Cost() float64 {
    return 1.9
}

func (e *Espresso) Desc() string {
    return "Espresso "
}

type HouseBlend struct {
}

func (h *HouseBlend) Cost() float64 {
    return 2.0
}
func (h *HouseBlend) Desc() string {
    return "HouseBlend "
}

//--------------附加功能(装饰类)--------
type Mocha struct {
    beverage Beverage
}

func NewMocha(b Beverage) Beverage {
    return &Mocha{b}
}

func (m *Mocha) Cost() float64 {
    return m.beverage.Cost() + 0.1
}

func (m *Mocha) Desc() string {
    return m.beverage.Desc() + "+ Mocha "
}

type Whip struct {
    beverage Beverage
}

func NewWhip(b Beverage) Beverage {
    return &Whip{b}
}
func (w *Whip) Cost() float64 {
    return w.beverage.Cost() + 0.2
}

func (w *Whip) Desc() string {
    return w.beverage.Desc() + "+ Whip "
}

type Soy struct {
    beverage Beverage
}

func NewSoy(b Beverage) Beverage {
    return &Soy{b}
}
func (s *Soy) Cost() float64 {
    return s.beverage.Cost() + 0.3
}

func (s *Soy) Desc() string {
    return s.beverage.Desc() + "+ Soy "
}

使用方在基本款的基础上任意组合:

func main() {
    e := &Espresso{}
    m := NewMocha(e)
    s := NewSoy(m)
    fmt.Println(s.Desc(), "=", s.Cost())

    h := &HouseBlend{}
    w := NewWhip(h)
    fmt.Println(w.Desc(), "=", w.Cost())
}

output:

➜  decorator go run main.go
Espresso + Mocha + Soy  = 2.3
HouseBlend + Whip  = 2.2

Decorator in real world

装饰器模式的核心是将一个类的功能附加在另外一个类的功能之上,但这里完全不要被面向对象中的class给迷惑,在golang中完全可以使用高阶函数来实现装饰器模式,且看http中的middlewaer模式。这也是好多框架实现的模式,在http请求进来之后做一系列的action(鉴权/审计)等等。 image

package main

import (
    "log"
    "net/http"
    "time"
)

const Key = "U3m9UT4sua"

func WithAuthicateCheck(h http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        k := r.Header.Get("Authorization")
        if k != Key {
            w.WriteHeader(401)
            return
        }
        h(w, r)
    }
}

func WithAudit(h http.HandlerFunc) http.HandlerFunc {
    return func(w http.ResponseWriter, r *http.Request) {
        start := time.Now()
        h(w, r)
        log.Printf("requsetURL = %s, elscaped = %s", r.URL.Path, time.Since(start))
    }
}

func HelloHandler(w http.ResponseWriter, r *http.Request) {
    time.Sleep(time.Second * 10)
    w.Write([]byte("Hello, World!"))
}

func main() {
    mux := http.NewServeMux()
    h := WithAudit(WithAuthicateCheck(HelloHandler))
    mux.Handle("/v1/hello", h)

    log.Printf("server is listening at %s", ":8080")
    log.Fatal(http.ListenAndServe(":8080", mux))
}

这里就是一个装饰器模式,在被装饰类 HelloHandler 上不断的附加middleware功能。