惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

J
Java Code Geeks
博客园 - 司徒正美
博客园 - 【当耐特】
爱范儿
爱范儿
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
IT之家
IT之家
人人都是产品经理
人人都是产品经理
雷峰网
雷峰网
酷 壳 – CoolShell
酷 壳 – CoolShell
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
大猫的无限游戏
大猫的无限游戏
月光博客
月光博客
宝玉的分享
宝玉的分享
V
V2EX
S
SegmentFault 最新的问题
V
Visual Studio Blog
阮一峰的网络日志
阮一峰的网络日志
Martin Fowler
Martin Fowler
Jina AI
Jina AI
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
博客园_首页
L
LangChain Blog
D
Docker
腾讯CDC

博客园 - 立志做一个好的程序员

【转】Golang使用时区时候报错 详解 Go 中的 rune 类型 【转】扫盲:Windows桌面应用开发框架:原生、跨平台、云桌面 Go-Zero自定义goctl实战:定制化模板,加速你的微服务开发效率 Go单体服务开发最佳实践 【转】Androidstudio报错Algorithm HmacPBESHA256 not available 借助ai来分析代码,理解代码 【转】linux设置密钥登录(只允许密钥登录) 【转】QPS和并发数的关系 【转】Command Pattern in Go (Golang) 【转】Raft 理论基础 【转】spin lock 和mutex [转]Go sync.Once:简约而不简单的并发利器 [转]Golang atomic.CompareAndSwapInt64()实例讲解 [转]手把手教你如何用golang实现一个timewheel时间轮 [转]Golang线程池实现百万级高并发 [转]Go网络编程 · 一条TCP连接讲透九大知识点 【转】,接上面3篇.Implement Sql Database Driver in 100 Lines of Go 【转】dive into golang database/sql(3)
[转]Golang Functional Options Pattern
立志做一个好的程序员 · 2024-03-12 · via 博客园 - 立志做一个好的程序员

原文: 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 {
  

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 {
  

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 {
  

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 {
  

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.