Go语言中基于接口的组合实践

Go语言的设计哲学之一:一切接组合。

Go语言中主要有两种组合方式:

①垂直组合:通过类型嵌入的方式

②水平组合:以接口类型变量作为水平组合的连接点。

垂直组合见另一篇文章:https://www.nowcoder.com/discuss/505073735309799424?sourceSSR=users。本篇实践水平组合。水平组合常见模式是使用接受接口类型参数的函数或方法。以下是以接口为连接点的水平组合的几种惯用形式。

1.基本形式

水平组合的基本形式是接受接口类型参数的函数或方法。其基本形式如下:

func FunctionName(param InterfaceType)

下面代码做实践展示:

import "fmt"

type MyInterface interface {
	Code() string
}

func Develop(m MyInterface, projectName string) {
	res := projectName + ": " + m.Code()
	fmt.Println(res)
}

type Student struct {
	Name  string
	Skill string
}

func (s *Student) Code() string {
	return fmt.Sprintf("Student %s can code with %s", s.Name, s.Skill)
}

type Programmer struct {
	Name         string
	LanguageNums int
	Languages    []string
}

func (p *Programmer) Code() string {
	return fmt.Sprintf("Programmer %s can code with %d languages: %v", p.Name, p.LanguageNums, p.Languages)
}

func main() {
	st := Student{"Anton", "Go"}
	Develop(&st, "demo")

	pr := Programmer{
		Name:         "Lisa",
		LanguageNums: 3,
		Languages:    []string{"Java", "C++", "Go"},
	}
	Develop(&pr, "demo")
}

// demo: Student Anton can code with Go
// demo: Programmer Lisa can code with 3 languages: [Java C++ Go]

如你所见,Develop()函数接受接口类型的参数(Student和Programmer均实现了MyInterface接口),将多个类型(例子中的Student和Programmer)组合到了一起。

2.包裹函数

包裹函数(wrapper function)接受接口类型参数,返回与其所接受的参数类型相同的返回值,其基本形式如下

func WrapperFunctionName(param InterfaceType) InterfaceType

下面是标准库中典型的包裹函数:io.LimitReader

func LimitReader(r Reader, n int64) Reader { return &LimitedReader{r, n} }

// A LimitedReader reads from R but limits the amount of
// data returned to just N bytes. Each call to Read
// updates N to reflect the new amount remaining.
// Read returns EOF when N <= 0 or when the underlying R returns EOF.
type LimitedReader struct {
	R Reader // underlying reader
	N int64  // max bytes remaining
}

func (l *LimitedReader) Read(p []byte) (n int, err error) {
	if l.N <= 0 {
		return 0, EOF
	}
	if int64(len(p)) > l.N {
		p = p[0:l.N]
	}
	n, err = l.R.Read(p)
	l.N -= int64(n)
	return
}

下面看个例子:将字符串转换为大写字母

import (
	"bytes"
	"fmt"
	"io"
	"log"
	"os"
)

func CapReader(r io.Reader) io.Reader {
	return capitalizeReader{r: r}
}

type capitalizeReader struct {
	r io.Reader
}

func (c capitalizeReader) Read(p []byte) (int, error) {
	n, err := c.r.Read(p)
	if err != nil {
		return 0, err
	}

	q := bytes.ToUpper(p)
	for i, v := range q {
		p[i] = v
	}
	return n, err
}

func main() {
	fmt.Println("Please enter a string:")

	s1 := CapReader(capitalizeReader{os.Stdin})
	if _, err := io.Copy(os.Stdout, s1); err != nil {
		log.Fatal(err)
	}
}

// Please enter a string:
// hello, gopher!
// HELLO, GOPHER!
// horizontal composition by interface.
// HORIZONTAL COMPOSITION BY INTERFACE.

上例中CapReader函数传入的参数为io.Reader接口类型,返回值类型依然是io.Reader接口类型。通过CapReader函数实现将输入字符串转换为大写。如此这样的函数称为包裹函数,实现对输入数据的过滤、装饰、变换等操作。

还可以将多个接受同一类型参数的包裹函数组合成一条链来使用,形式如下:

func WrapperFunctionName1(WrapperFunctionName2(...)) InterfaceType
func main() {
	fmt.Println("Please enter a string:")

	s1 := CapReader(capitalizeReader{io.LimitReader(os.Stdin, 5)})
	if _, err := io.Copy(os.Stdout, s1); err != nil {
		log.Fatal(err)
	}
}

// Please enter a string:
// hello, gopher!
// HELLO

3.适配器函数类型

适配器函数类型(adapter function type)是一个辅助水平组合实现的”工具“类型,它是一个类型。适配器函数类型可以将一个满足特定函数签名的普通函数显式转换为自身类型的实例,转换后的实例同时也是某个单方法接口类型的实现者。

最典型的适配器函数类型就是http包中的HandlerFunc:

type Handler interface {
	ServeHTTP(ResponseWriter, *Request)
}

// Handler that calls f.
type HandlerFunc func(ResponseWriter, *Request)

// ServeHTTP calls f(w, r).
func (f HandlerFunc) ServeHTTP(w ResponseWriter, r *Request) {
	f(w, r)
}

通过HandlerFunc这个适配器函数,可以将普通函数转换为实现了Handler接口的类型。转换后,便可以将函数的实例用作实参,实现基于接口的组合:

import (
	"fmt"
	"net/http"
)

func greetings(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Welcome")
}

func main() {
	http.ListenAndServe("8080", http.HandlerFunc(greetings))
}

4.中间件

中间件(middleware):包裹函数和适配器类型函数结合的产物。

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

func greetings(w http.ResponseWriter, r *http.Request) {
	fmt.Fprintf(w, "Welcome")
}

func validatedAuth(s string) error {
	if s != "123456" {
		return fmt.Errorf("invalid auth: %s", s)
	}
	return nil
}

func logHandler(h http.Handler) http.Handler {
	return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
		t := time.Now()
		log.Printf("[%s] %q %v\n", request.Method, request.URL.String(), t)
		h.ServeHTTP(writer, request)
	})
}

func authHandler(h http.Handler) http.Handler {
	return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
		err := validatedAuth(request.Header.Get("auth"))
		if err != nil {
			http.Error(writer, "invalid auth", http.StatusUnauthorized)
			return
		}
		h.ServeHTTP(writer, request)
	})
}

func main() {
	http.ListenAndServe(":8080", logHandler(authHandler(http.HandlerFunc(greetings))))
}

如上例中中间件(logHandler、authHandler)本质上就是一个包裹函数,但其内部利用了适配器函数类型(http.HandlerFunc)将一个普通函数转换为实现了http.Handler的类型的实例,并将其作为返回值。

全部评论

相关推荐

剑桥断刀:找啥工作,牛客找个比如大厂软开或者随便啥的高薪牛马,大把没碰过妹子的技术仔,狠狠拿捏爆金币
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客企业服务