Decorator Design Pattern
修饰器模式(Decorator Design Pattern)是一种结构型设计模式。意图动态地为原始对象添加一层层附加能力,以完成对原始对象既有功能的扩展和增强。
具体的修饰器应该遵循一个契约:持有 A 接口的实例,同时本身必须实现 A 接口。
由于目标实例和修饰器遵循同一接口,因此可用修饰器来对实例进行无限次的封装。结果实例将获得所有修饰器叠加而来的行为。
目标对象可以是一个结构,也可以是一个函数。与之相对,可以分别通过结构组合(面向对象编程范式)或者高阶函数(函数式编程范式)的方式实现这个模式。
UML Diagram
- Client 为主调方,依赖 Component 接口;
- Concrete Component 是实现了 Component 接口的结构;
- Decorator 定义了实现契约的“抽象类”:
- 修饰器必须内嵌一个实现了 Component 接口的实例;
- 修饰器本身必须实现 Component 接口;
- Concrete Decorator x 是遵循 Decorator 契约的结构;
修饰器模式也是一种 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