装饰器模式
装饰器模式可以在不修改原有对象的基础上,通过创建一个包装对象来扩展其功能。这种模式创建了一个装饰类,用来包装原有类,并在保持原类方法签名完整性的前提下提供了额外的功能。
装饰器模式的优点:
- 扩展性:可以在不修改原有对象代码的情况下,通过装饰类来扩展其功能。
- 灵活性:可以动态地为对象添加功能。
- 符合开闭原则:对扩展开放,对修改封闭。
引入
现在我们有这么一个代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| func HelloWorld(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte("Hello, world!")) }
func HowAreYour(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte("How are you?")) }
func main() { mux := http.NewServeMux()
mux.HandleFunc("/hello", HelloWorld) mux.HandleFunc("/howareyou", HowAreYour)
srv := &http.Server{ Addr: ":8080", Handler: mux, }
_ = srv.ListenAndServe() }
|
当我们需要为每个路由都实现一个记录日志的操作:
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
| func HelloWorld(w http.ResponseWriter, r *http.Request) { log.Printf("Hello, world!") w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte("Hello, world!")) }
func HowAreYour(w http.ResponseWriter, r *http.Request) { log.Printf("How are you?") w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte("How are you?")) }
func main() { mux := http.NewServeMux()
mux.HandleFunc("/hello", HelloWorld) mux.HandleFunc("/howareyou", HowAreYour)
srv := &http.Server{ Addr: ":8080", Handler: mux, }
_ = srv.ListenAndServe() }
|
但是当路由增多的时候,我们就不得不为每个路由都添加上这么一行重复的代码,非常麻烦,因此我们需要使用装饰器模式来简洁我们的代码
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
| type Handler func(w http.ResponseWriter, r *http.Request)
func Logger(handler Handler) Handler { return func(w http.ResponseWriter, r *http.Request) { now := time.Now() handler(w, r) log.Println("Request processed in ", time.Since(now)) } }
func HelloWorld(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte("Hello, world!")) }
func HowAreYour(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte("How are you?")) }
func main() { mux := http.NewServeMux()
mux.HandleFunc("/hello", Logger(HelloWorld)) mux.HandleFunc("/howareyou", Logger(HowAreYour))
srv := &http.Server{ Addr: ":8080", Handler: mux, }
_ = srv.ListenAndServe() }
|
中间件
如果我们想要实现一个简单的中间件,我们可以写成这样:
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
| func Logger(next http.Handler) http.Handler { fn := func(w http.ResponseWriter, r *http.Request) { time.Now() next.ServeHTTP(w, r) fmt.Println("url: ", r.URL.Path) } return http.HandlerFunc(fn) }
func HelloWorld(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte("Hello, world!")) }
func HowAreYour(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte("How are you?")) }
func main() { mux := http.NewServeMux()
mux.HandleFunc("/hello", HelloWorld) mux.HandleFunc("/howareyou", HowAreYour)
srv := &http.Server{ Addr: ":8080", Handler: Logger(mux), }
_ = srv.ListenAndServe() }
|
Gin
下面的东西仅供参考:
在 Go 语言中,中间件通常用于处理 HTTP 请求和响应的前置和后置操作。虽然 Go 语言没有像 Python 或 Java 那样的显式装饰器模式,但是可以通过组合和接口来实现类似的功能。
以下是如何在 Go 语言中实现中间件的装饰器模式的步骤:
1. 定义 Handler 接口
首先,定义一个 Handler 接口,它包含处理 HTTP 请求的方法。
go
1 2 3 4 5 6 7 8 9 10
| package main
import ( "net/http" )
type Handler interface { ServeHTTP(w http.ResponseWriter, r *http.Request) }
|
2. 创建具体 Handler
实现一个具体的 Handler,它将处理实际的请求。
go
1 2 3 4 5 6
| type ConcreteHandler struct{}
func (h *ConcreteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Handling request...")) }
|
3. 创建装饰器接口
定义一个装饰器接口,它包含一个接受 Handler 并返回新 Handler 的方法。
go
1 2 3 4
| type Decorator interface { Decoration(h Handler) Handler }
|
4. 实现具体装饰器
创建具体的装饰器,它们将包装 Handler 并添加额外的功能。
go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| type LoggingDecorator struct{}
func (d *LoggingDecorator) Decoration(h Handler) Handler { return &loggingHandler{h} }
type loggingHandler struct { Handler }
func (h *loggingHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { println("Before handling request") h.Handler.ServeHTTP(w, r) println("After handling request") }
|
5. 使用装饰器
组合具体的 Handler 和装饰器来创建最终的 Handler。
go
1 2 3 4 5 6 7 8 9 10 11 12 13
| func main() { handler := &ConcreteHandler{}
decorator := &LoggingDecorator{} finalHandler := decorator.Decoration(handler)
http.Handle("/", finalHandler) println("Starting server on :8080") http.ListenAndServe(":8080", nil) }
|
完整代码
go
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
| package main
import ( "fmt" "net/http" )
type Handler interface { ServeHTTP(w http.ResponseWriter, r *http.Request) }
type ConcreteHandler struct{}
func (h *ConcreteHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { w.Write([]byte("Handling request...")) }
type Decorator interface { Decoration(h Handler) Handler }
type LoggingDecorator struct{}
func (d *LoggingDecorator) Decoration(h Handler) Handler { return &loggingHandler{h} }
type loggingHandler struct { Handler }
func (h *loggingHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { fmt.Println("Before handling request") h.Handler.ServeHTTP(w, r) fmt.Println("After handling request") }
func main() { handler := &ConcreteHandler{}
decorator := &LoggingDecorator{} finalHandler := decorator.Decoration(handler)
http.Handle("/", finalHandler) fmt.Println("Starting server on :8080") http.ListenAndServe(":8080", nil) }
|
在这个例子中,ConcreteHandler
是一个具体的 Handler,LoggingDecorator
是一个装饰器,它添加了日志记录功能。通过组合装饰器和 Handler,我们可以动态地为 Handler 添加新的行为。
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
| return func(c *Context) { start := time.Now() path := c.Request.URL.Path raw := c.Request.URL.RawQuery
c.Next()
if _, ok := skip[path]; ok || (conf.Skip != nil && conf.Skip(c)) { return }
param := LogFormatterParams{ Request: c.Request, isTerm: isTerm, Keys: c.Keys, }
param.TimeStamp = time.Now() param.Latency = param.TimeStamp.Sub(start)
param.ClientIP = c.ClientIP() param.Method = c.Request.Method param.StatusCode = c.Writer.Status() param.ErrorMessage = c.Errors.ByType(ErrorTypePrivate).String()
param.BodySize = c.Writer.Size()
if raw != "" { path = path + "?" + raw }
param.Path = path
fmt.Fprint(out, formatter(param)) }
|
}