开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 7 天,点击查看活动详情
接口背景
接口设计的一些原则:
- 在实际真正需要的时候才对程序进行抽象,不要为了抽象而抽象
- 接口的副作用是提高的代码的抽象程度,降低运行效率和可读性
好的设计模式的特点:
- 正交:解耦性,对输入的参数,各自独立,一个改变不会影响其他的。
静态骨架结构的两种组合方式
垂直组合
interface、struct的匿名字段嵌入 垂直组合更多应用在新类型的定义方面,可以达到方法实现的复用、接口定义重用等目的。
水平组合
通过函数传入接口,实现不同接口间的连接,因为使用的是接口类型的参数,基于最小接口原则,提供了很好的拓展性和正交性。 例如下面的代码:
func Save(w io.Writer, data []byte) error
所有实现io.Writer接口的实例都可以调用Save方法,例如io、file、string buffer等
常见的接口设计模式
基本模式
基本的水平组合:接受接口类型参数的函数或方法
func YourFuncName(param YourInterfaceType)
这里注意:
- 参数的接口类型是传入实体接口类型的子集,相当于可以用共同行为的对象进行替换,像关节一样灵活。
- 满足:依赖抽象(DIP)、里氏替换原则(LSP)、接口隔离(ISP)等代码设计原则
创建模式
接受接口,返回结构体(Accept interfaces, return structs),常用于接口类型字段的结构体的实例化。
type M struct{
xxx xxx
reader io.Reader
}
fun New(reader io.Reader,xxx xxx) *M{
return &M{reader,xxx}
}
包装器模式
在基本模式的基础上,返回值的类型与参数类型相同。实现对输入参数的类型的包装,并在不改变被包装类型(输入参数类型)的定义的情况下,返回具备新功能特性的、实现相同接口类型的新类型。包装器多用于对输入数据的过滤、变换等操作。
下面演示了,对io.Reader进行包装,在调用其Read方法时,添加了长度拦截的功能(不破坏原方法的签名)。
func YourWrapperFunc(param YourInterfaceType) YourInterfaceType
// 小栗子
type M struct{
xxx xxx
reader io.Reader
}
type LimitRead struct{
r io.Reader
limit int
}
func (r *LimitRead)Read() string{
s:=r.r.Read()
return s[:r.limit]
}
fun New(reader io.Reader,n int) io.Reader{
return &LimitRead{reader,n}
}
可以将多个接受同一接口类型参数的包装函数组合成一条链来调用
YourWrapperFunc1(YourWrapperFunc2(YourWrapperFunc3(...)))
适配器模式
适配器函数类型是一个辅助水平组合实现的“工具”类型。这里我要再强调一下,它是一个类型。它可以将一个满足特定函数签名的普通函数,显式转换成自身类型的实例,转换后的实例同时也是某个接口类型的实现者。
http源码的示例:
func greetings(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Welcome!")
}
func main() {
http.ListenAndServe(":8080", http.HandlerFunc(greetings))
}
// $GOROOT/src/net/http/server.go
type Handler interface {
ServeHTTP(ResponseWriter, *Request)
}
type HandlerFunc func(ResponseWriter, *Request)
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
f(w, r)
}
上述代码http.ListenAndServe需要一个Handler类型的参数,会调用这个类型的ServeHTTP方法。http.HandlerFunc就是一个适配器类型,提供类型转化,可以将任何符合这个函数签名的函数通过类型转化传给http.ListenAndServe,然后可以后续调用。
emmm,为啥不直接传这个greetings函数,http直接调用greetings????
中间件(Middleware)
在 Go Web 编程中,“中间件”常常指的是一个实现了 http.Handler 接口的 http.HandlerFunc 类型实例。实质上,这里的中间件就是包装模式和适配器模式结合的产物。 小栗子:
func validateAuth(s string) error {
if s != "123456" {
return fmt.Errorf("%s", "bad auth token")
}
return nil
}
func greetings(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Welcome!")
}
func logHandler(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
t := time.Now()
log.Printf("[%s] %q %v\n", r.Method, r.URL.String(), t)
h.ServeHTTP(w, r)
})
}
func authHandler(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
err := validateAuth(r.Header.Get("auth"))
if err != nil {
http.Error(w, "bad auth param", http.StatusUnauthorized)
return
}
h.ServeHTTP(w, r)
})
}
func main() {
http.ListenAndServe(":8080", logHandler(authHandler(http.HandlerFunc(greetings))))
}
上述http.HandlerFunc(greetings)就是适配器模式,下面在外边套了两个中间件(输入和输出为适配器模式类型的函数)logHandler和authHandler,分别实现了打印时间和身份验证,相当于对适配器打了两个补丁。 综上所述,中间件就是适配器的包装器。