首页 > 其他分享 >[转]Golang Functional Options Pattern

[转]Golang Functional Options Pattern

时间:2024-03-12 11:48:23浏览次数:25  
标签:Pattern port Functional Server Golang host func server options

 

原文: https://golang.cafe/blog/golang-functional-options-pattern.html

--------------------

 

Golang Functional Options Pattern

The Go (Golang) Functiona Options Pattern is a way, a pattern of structuring your structs in Go by designing a very expressive and flexible set of APIs that will help with the configuration and initialisation of your struct. Let’s have a look at a code snippet and let’s see what options we can use and how and when the functional options pattern can be useful for us.

Example: Building a server package in Go

In this example we look at a server package in Go, but it could be anything that is used by a third party client, like a custom SDK, or a logger library.

package server

type Server {
  host string
  port int
}

func New(host string, port int) *Server {
  return &Server{host, port}
}

func (s *Server) Start() error {
  // todo
}

And here’s how a client would import and use your server package

package main

import (
  "log"
  
  "github.com/acme/pkg/server"
)

func main() {
  svr := server.New("localhost", 1234)
  if err := svr.Start(); err != nil {
    log.Fatal(err)
  }
}

Now, given this scenario, how do we extend configuration options for our server? There are a few options

  • Declare new a constructor for each different configuration option
  • Define a new Config struct that holds configuration information
  • Use the Functional Option Pattern

Let’s explore these 3 examples one by one and analyse the pros and cons of each.

Option 1: Declare a new constructor for each configuration option

This can be a good approach if you know that your configuration options are not luckily to be going to change and if you have very few of them. So it will be easy to just create new methods for each different configuration option.

package server

type Server {
  host string
  port int
  timeout time.Duration
  maxConn int
}

func New(host string, port int) *Server {
  return &Server{host, port, time.Minute, 100}
}

func NewWithTimeout(host string, port int, timeout time.Duration) *Server {
  return &Server{host, port, timeout}
}

func NewWithTimeoutAndMaxConn(host string, port int, timeout time.Duration, maxConn int) *Server {
  return &Server{host, port, timeout, maxConn}
}

func (s *Server) Start() error {
  // todo
}

And the relative client implementation below

package main

import (
  "log"
  
  "github.com/acme/pkg/server"
)

func main() {
  svr := server.NewWithTimeoutAndMaxConn("localhost", 1234, 30*time.Second, 10)
  if err := svr.Start(); err != nil {
    log.Fatal(err)
  }
}

This approach is not very flexible when the number of configuration options grow or changes often. You will also need to create new constructors with each new configuration option or set of configuration options.

Option 2: Use a custom Config struct

This is the most common approach and can work well when there are a lot of options to configure. You can create a new exported type called “Config” which contains all the configuration options for your server. This can be extended easily without breaking the server constructor APIs. We won’t have to change its definition when new options are added or old ones are removed

package server

type Server {
  cfg Config
}

type Config struct {
  host string
  port int
  timeout time.Duration
  maxConn int
}

func New(cfg Config) *Server {
  return &Server{cfg}
}

func (s *Server) Start() error {
  // todo
}

And the relative client implementation below using the new Config struct

package main

import (
  "log"
  
  "github.com/acme/pkg/server"
)

func main() {
  svr := server.New(server.Config{"localhost", 1234, 30*time.Second, 10})
  if err := svr.Start(); err != nil {
    log.Fatal(err)
  }
}

This approach is flexible in a way that allows us to define a fixed type (server.Config) for our server (or SDK client or anything you are building) and a stable set of APIs to configure our server like server.New(cfg server.Config). The only issue is that we will still need to make breaking changes to the structure of our Config struct when new options are added or old ones are being removed. But this is still the best and more usable option so far.

Option 3: Functional Options Pattern

A better alternative to this options configuration problem is exaclty the functional options design pattern. You may have seen or heard the functional options pattern in Go projects before but in this example we are going to breakdown the structure and the characteristics of it in detail.

package server

type Server {
  host string
  port int
  timeout time.Duration
  maxConn int
}

func New(options ...func(*Server)) *Server {
  svr := &Server{}
  for _, o := range options {
    o(svr)
  }
  return svr
}

func (s *Server) Start() error {
  // todo
}

func WithHost(host string) func(*Server) {
  return func(s *Server) {
    s.host = host
  }
}

func WithPort(port int) func(*Server) {
  return func(s *Server) {
    s.port = port
  }
}

func WithTimeout(timeout time.Duration) func(*Server) {
  return func(s *Server) {
    s.timeout = timeout
  }
}

func WithMaxConn(maxConn int) func(*Server) {
  return func(s *Server) {
    s.maxConn = maxConn
  }
}

And the relative client implementation below using the new functional option pattern

package main

import (
  "log"
  
  "github.com/acme/pkg/server"
)

func main() {
  svr := server.New(
    server.WithHost("localhost"),
    server.WithPort(8080),
    server.WithTimeout(time.Minute),
    server.WithMaxConn(120),
  )
  if err := svr.Start(); err != nil {
    log.Fatal(err)
  }
}

The functional options pattern allows us to define a fixed type signature for each and any possible configuration of our server, buy using the func(*Server) type signature we can create any option to be passed to the server. Our options are also optional by default, so it’s easy to swap any options without any major problem. This approach is also good given the expressive design and the auto-documenting nature of the type definitions, each method defines the option and the type of option for your server.

标签:Pattern,port,Functional,Server,Golang,host,func,server,options
From: https://www.cnblogs.com/oxspirt/p/18067947

相关文章

  • golang练习题
    看到一个网站,上面每天发布一道golang练习题,正好拿来练习,顺便整理记录下来。iota,类似枚举值,每个const从0开始计数 String方法相当于java里的toStringgolang处于安全考虑,对指针运算做了很多限制。map的value是不可以取地址的。 ......
  • golang 接口
    接口学习Go语言中的接口时,以下是你需要关注的主要概念和知识点:在Go语言中使用隐式声明的方式实现接口。只要一个类型实现了接口中规定的所有方法,那么它就实现了这个接口1.接口定义接口是一种类型,定义了一组方法的集合。接口定义的方法不包含实现,只有方法签名。示例:Goty......
  • 运行golang测试无法读取环境变量[vscode]
    使用vscode运行golang测试,通常我们会发现无法读取到设置在系统的环境变量,其本质原因是使用vscode启动testing并不是常规的subshell,无法正常读取到系统的环境变量;解决方案:方案1:将环境变量配置在setting.json(适用于变量较少情况)"go.testEnvVars":{"NAME":"zimskyzeng",},......
  • golang基于长度解决粘包问题(gnet)
    使用gnet框架处理Socket粘包问题当服务端处理旧业务tcpscoket,旧的业务是NettySocket使用的是2个字节的长度定义数据的大小。官方支持ICodec去处理,但文档不太友好,这里附上使用方法import( "github.com/panjf2000/gnet")typeDTUSocketServerstruct{ *gnet.EventServer......
  • 4-3nn.functional和nn.Module
    importtorchimporttorchkerasprint("torch.__version__="+torch.__version__)print("torchkeras.__version__="+torchkeras.__version__)"""torch.__version__=2.1.1+cu118torchkeras.__version__=3.9.4"""1......
  • golang开发_goroutine在项目中的使用姿势
    很多初级的Gopher在学习了goroutine之后,在项目中其实使用率不高,尤其一些跨语言过来的人,对并发编程理解不深入,可能很多人只知道gofunc(),或者掌控不够,谨慎一些,尽量少使用或者不使用,用的话就是gofunc(),主要列一下我们这边的主要使用方法。goroutine在项目中的使用方法看一下样......
  • golang结构体
    在Go语言中,结构体(Struct)是一种用户定义的数据类型,用于组合多个不同类型的字段,每个字段可以是任意的基本类型或其他结构体类型。结构体是一种复合数据类型,用于组织和存储相关的数据。以下是结构体的一些基本概念和用法:1.定义结构体//定义一个结构体typePersonstruct{Firs......
  • golang入门概览
    一、语法golang中数据类型有值类型(boolintstring)和派生类型(structinterface等等)。值类型通常在栈上分配内存,派生类型通常在堆上分配内存。golang中的函数调用,只有值传递,没有引用传递。但是golang提供了指针,使用指针可以达到引用传参的效果,在函数内部修改参数后可以在外......
  • golang将时间转为时间戳碰到的问题
    golang将字符串"2024-03-0716:00:00"转为时间戳代码如下:packagemainimport("fmt""time")funcmain(){//定义时间格式,与字符串中的时间格式匹配constlayout="2006-01-0215:04:05"//要转换的时间字符......
  • golang进阶之反射
    目录一、go中变量的内在机制二、反射1.反射是把双刃剑2.反射的简介三、reflect库1.reflect.TypeOf(1)reflect.Type的name和kind(2)kind的能返回的类型如下2.reflect.ValueOf(1)反射取值(2)反射改值3.isNil()和isValid()四、结构体的反射1.StructField类型2.结构体反射示例(1......