Decorator Design Pattern

修饰器模式(Decorator Design Pattern)是一种结构型设计模式。意图动态地为原始对象添加一层层附加能力,以完成对原始对象既有功能的扩展和增强

具体的修饰器应该遵循一个契约:持有 A 接口的实例,同时本身必须实现 A 接口。 由于目标实例和修饰器遵循同一接口,因此可用修饰器来对实例进行无限次的封装。结果实例将获得所有修饰器叠加而来的行为。

目标对象可以是一个结构,也可以是一个函数。与之相对,可以分别通过结构组合(面向对象编程范式)或者高阶函数(函数式编程范式)的方式实现这个模式。

UML Diagram

image

修饰器模式也是一种 Wrapper 模式,使用这种设计模式可以很方便得实现“俄罗斯套娃”,完成对既有功能的扩展增强。

Example 1 (面向对象编程范式)

Go 实现的版本是通过组合玩法,创建内嵌 Component 实例的 Decorator 结构(本身也实现 Component 接口)来完成对既有接口的包装和扩展。

Question:

香河肉饼准备拓展业务卖披萨饼,暂定
原味饼底 10 元,
Cocoa 配料 20 元,
Fruit 配料 25 元,
饼底和口味(可叠加)随意组合,应如何设计?

Code:

 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
package main

import "fmt"

type IPizza interface {
	getTaste() []string
	getPrice() int
}

type Crepes struct{}

func (p *Crepes) getTaste() []string { return []string{"default"} }
func (p *Crepes) getPrice() int      { return 10 }

// Topping 是一个实现了 IPizza 的 decorator
type Topping struct {
	pizza IPizza 	// 内嵌一个 IPizza 实例
	taste string
	price int
}

// WithTopping 通过不同参数创建不同 concrete decorator
func WithTopping(pizza IPizza, taste string, price int) *Topping {
	return &Topping{pizza: pizza, taste: taste, price: price}
}

func (t *Topping) getTaste() []string {
	result := t.pizza.getTaste()
	result = append(result, t.taste)
	return result
}
func (t *Topping) getPrice() int {
	return t.pizza.getPrice() + t.price
}

func main() {
	// 套娃第 0 层:原味薄饼
	pizza := &Crepes{}
	// 套娃第 1 层:添加 cocoa 配料
	pizzaWithCocoa := WithTopping(pizza, "cocoa", 20)
	// 套娃第 2 层:添加 fruit 配料
	pizzaWithCocaAndFruit := WithTopping(pizzaWithCocoa, "fruit", 25)
	fmt.Printf("price of crepes + toppings:%s is: %d\n",
		pizzaWithCocaAndFruit.getTaste(),
		pizzaWithCocaAndFruit.getPrice(),
	)
}

Output:

price of crepes + toppings:[default cocoa fruit] is: 55

Example 2 (函数式编程范式)

谈及修饰器模式,我们一直在强调功能。将个逻辑概念映射到编程语言领域,功能即函数。Interface 便是一组函数签名。 针对接单个功能的无限修饰,也可以通过高阶函数来实现。这种实现方式在 Go 中更为常见。

Question:

请求 /v1/hello 给响应携带 ServerHeader
请求 /v2/hello 给响应携带 ServerHeader,AuthCookie
请求 /v3/hello 给响应携带 ServerHeader,AuthCookie,BasicAuth

Code:

 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
package main

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

// WithServerHeader 是一个 Concrete Decorator,传入一个 http.HandlerFunc,返回一个改写的版本。
func WithServerHeader(h http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		log.Println("--->Before WithServerHeader()")
		w.Header().Set("Server", "HelloServer v0.0.1")
		h(w, r)
		log.Println("--->After WithServerHeader()")
	}
}

// WithAuthCookie is another Concrete Decorator
func WithAuthCookie(h http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		log.Println("--->Before WithAuthCookie()")
		cookie := &http.Cookie{Name: "Auth", Value: "Pass", Path: "/"}
		http.SetCookie(w, cookie)
		h(w, r)
		log.Println("--->After WithAuthCookie()")
	}
}

// WithBasicAuth is another Concrete Decorator
func WithBasicAuth(h http.HandlerFunc) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		log.Println("--->Before WithBasicAuth()")
		cookie, err := r.Cookie("Auth")
		if err != nil || cookie.Value != "Pass" {
			w.WriteHeader(http.StatusForbidden)
			return
		}
		h(w, r)
		log.Println("--->After WithBasicAuth()")
	}
}

func hello(w http.ResponseWriter, r *http.Request) {
	log.Printf("Recieved Request %s from %s\n", r.URL.Path, r.RemoteAddr)
	fmt.Fprintf(w, "Hello, World! "+r.URL.Path)
}

func main() {
	http.HandleFunc("/v1/hello", WithServerHeader(hello)
	http.HandleFunc("/v2/hello", WithServerHeader(WithAuthCookie(hello)))
	http.HandleFunc("/v3/hello", WithServerHeader(WithAuthCookie(WithBasicAuth(hello))))
	err := http.ListenAndServe(":8080", nil)
	if err != nil {
		log.Fatal("ListenAndServe: ", err)
	}
}

Refactor:

针对当前丑陋的嵌套形式进行重构,提炼出一个工具函数来实现多个修饰器的 Pipeline。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// 定义 Decorator 契约
type HttpHandlerDecorator func(http.HandlerFunc) http.HandlerFunc

// 控制逻辑分离,通过提炼出工具函数类几种遍历所有修饰器
func Handler(h http.HandlerFunc, decors ...HttpHandlerDecorator) http.HandlerFunc {
    for i := range decors {
        d := decors[len(decors)-1-i] // iterate in reverse
        h = d(h)
    }
    return h
}

重构之后便可以如下格式拼装

http.HandleFunc("/v3/hello", Handler(hello, WithServerHeader, WithAuthCookie, WithBasicAuth))

Output:

2021/04/26 11:02:57 --->Before WithServerHeader()
2021/04/26 11:02:57 --->Before WithAuthCookie()
2021/04/26 11:02:57 --->Before WithBasicAuth()
2021/04/26 11:02:57 Recieved Request /v3/hello from [::1]:54835
2021/04/26 11:02:57 --->After WithBasicAuth()
2021/04/26 11:02:57 --->After WithAuthCookie()
2021/04/26 11:02:57 --->After WithServerHeader()

Application Scenarios

References

https://time.geekbang.org/column/article/332608