Go语言设计模式之函数式选项模式

背景:

在看OpenTelemetry-gin源码时遇到一个函数
func Middleware(service string, opts ...Option) gin.HandlerFunc
可以看到这个函数是以Option为参数的;
点进去发现Option是一个接口,

type Option interface {
	apply(*config)
}

Middleware内是这样使用该参数的:

func Middleware(service string, opts ...Option) gin.HandlerFunc {
	cfg := config{}
	for _, opt := range opts {
		opt.apply(&cfg)
	}

综上不难发现,如果我们要成功调用Middleware函数,就要实现一个Option类,该类主要用applay方法来进行参数的装配;

调研

调研发现这是go的一种设计模式:选项模式

1.一般的选项模式都是函数选项模式,例子如下:

我们先定义一个OptionFunc的函数类型

type OptionFunc func(*Option)

然后利用闭包为每个字段编写一个设置值的With函数:

func WithA(a string) OptionFunc {
	return func(o *Option) {
		o.A = a
	}
}

func WithB(b string) OptionFunc {
	return func(o *Option) {
		o.B = b
	}
}

func WithC(c int) OptionFunc {
	return func(o *Option) {
		o.C = c
	}
}

然后,我们定义一个默认的Option如下:

var (
	defaultOption = &Option{
		A: "A",
		B: "B",
		C: 100,
	}
)

最后编写我们新版的构造函数如下:

func newOption2(opts ...OptionFunc) (opt *Option) {
	opt = defaultOption
	for _, o := range opts {
		o(opt)
	}
	return
}

测试一下:

func main() {
	x := newOption("nazha", "小王子", 10)
	fmt.Println(x)
	x = newOption2()
	fmt.Println(x)
	x = newOption2(
		WithA("沙河娜扎"),
		WithC(250),
	)
	fmt.Println(x)
}

输出:

&{nazha 小王子 10}
&{A B 100}
&{沙河娜扎 B 250}
2.openTelemetry-gin中用到的也是函数选项模式(也可称为结构体选项模式)

接口

type Option interface {
	apply(*config)
}

调用

func Middleware(service string, opts ...Option) gin.HandlerFunc {
	cfg := config{}
	for _, opt := range opts {
		opt.apply(&cfg)
	}
	if cfg.TracerProvider == nil {
		cfg.TracerProvider = otel.GetTracerProvider()
	}
	tracer := cfg.TracerProvider.Tracer(
		tracerName,
		oteltrace.WithInstrumentationVersion(SemVersion()),
	)
	if cfg.Propagators == nil {
		cfg.Propagators = otel.GetTextMapPropagator()
	}
	return func(c *gin.Context) {
		c.Set(tracerKey, tracer)
		savedCtx := c.Request.Context()
		defer func() {
			c.Request = c.Request.WithContext(savedCtx)
		}()
		ctx := cfg.Propagators.Extract(savedCtx, propagation.HeaderCarrier(c.Request.Header))
		opts := []oteltrace.SpanStartOption{
			oteltrace.WithAttributes(semconv.NetAttributesFromHTTPRequest("tcp", c.Request)...),
			oteltrace.WithAttributes(semconv.EndUserAttributesFromHTTPRequest(c.Request)...),
			oteltrace.WithAttributes(semconv.HTTPServerAttributesFromHTTPRequest(service, c.FullPath(), c.Request)...),
			oteltrace.WithSpanKind(oteltrace.SpanKindServer),
		}
		spanName := c.FullPath()
		if spanName == "" {
			spanName = fmt.Sprintf("HTTP %s route not found", c.Request.Method)
		}
		ctx, span := tracer.Start(ctx, spanName, opts...)
		defer span.End()

		// pass the span through the request context
		c.Request = c.Request.WithContext(ctx)

		// serve the request to the next middleware
		c.Next()

		status := c.Writer.Status()
		attrs := semconv.HTTPAttributesFromHTTPStatusCode(status)
		spanStatus, spanMessage := semconv.SpanStatusFromHTTPStatusCodeAndSpanKind(status, oteltrace.SpanKindServer)
		span.SetAttributes(attrs...)
		span.SetStatus(spanStatus, spanMessage)
		if len(c.Errors) > 0 {
			span.SetAttributes(attribute.String("gin.errors", c.Errors.String()))
		}
	}
}

可以看到这种方法本质上还是进行方法的调用,不同的是可以将一组属性封装到一个apply中,不用一个属性写一个函数装配;

参考:

  1. https://www.liwenzhou.com/posts/Go/functional_options_pattern/

你可能感兴趣的:(Go,设计模式,设计模式,java,开发语言)