首页 > 编程语言 >GO 编程模式:FUNCTIONAL OPTIONS

GO 编程模式:FUNCTIONAL OPTIONS

时间:2023-08-01 14:22:41浏览次数:40  
标签:return 编程 FUNCTIONAL Server Go func GO sb OPTIONS

在本篇文章中,我们来讨论一下Functional Options这个编程模式。这是一个函数式编程的应用案例,编程技巧也很好,是目前在Go语言中最流行的一种编程模式。但是,在我们正式讨论这个模式之前,我们需要先来看看要解决什么样的问题。

本文是全系列中第3 / 10篇:Go编程模式

« 上一篇文章下一篇文章 »

目录

配置选项问题

在我们编程中,我们会经常性的需要对一个对象(或是业务实体)进行相关的配置。比如下面这个业务实体(注意,这仅只是一个示例):

type Server struct { Addr string Port int Protocol string Timeout time.Duration MaxConns int TLS *tls.Config }  

在这个 Server 对象中,我们可以看到:

 

  • 要有侦听的IP地址 Addr 和端口号 Port ,这两个配置选项是必填的(当然,IP地址和端口号都可以有默认值,当这里我们用于举例认为是没有默认值,而且不能为空,需要必填的)。
  • 然后,还有协议 Protocol 、 Timeout 和MaxConns 字段,这几个字段是不能为空的,但是有默认值的,比如:协议是tcp, 超时30秒 和 最大链接数1024个。
  • 还有一个 TLS 这个是安全链接,需要配置相关的证书和私钥。这个是可以为空的。

所以,针对于上述这样的配置,我们需要有多种不同的创建不同配置 Server 的函数签名,如下所示(代码比较宽,需要左右滚动浏览):

func NewDefaultServer(addr string, port int) (*Server, error) { return &Server{addr, port, "tcp", 30 * time.Second, 100, nil}, nil }   func NewTLSServer(addr string, port int, tls *tls.Config) (*Server, error) { return &Server{addr, port, "tcp", 30 * time.Second, 100, tls}, nil }   func NewServerWithTimeout(addr string, port int, timeout time.Duration) (*Server, error) { return &Server{addr, port, "tcp", timeout, 100, nil}, nil }   func NewTLSServerWithMaxConnAndTimeout(addr string, port int, maxconns int, timeout time.Duration, tls *tls.Config) (*Server, error) { return &Server{addr, port, "tcp", 30 * time.Second, maxconns, tls}, nil }  

因为Go语言不支持重载函数,所以,你得用不同的函数名来应对不同的配置选项。

配置对象方案

要解决这个问题,最常见的方式是使用一个配置对象,如下所示:

type Config struct { Protocol string Timeout time.Duration Maxconns int TLS *tls.Config }

我们把那些非必输的选项都移到一个结构体里,于是 Server 对象变成了:

type Server struct { Addr string Port int Conf *Config }

于是,我们只需要一个 NewServer() 的函数了,在使用前需要构造 Config 对象。

func NewServer(addr string, port int, conf *Config) (*Server, error) { //... }   //Using the default configuratrion srv1, _ := NewServer("localhost", 9000, nil)   conf := ServerConfig{Protocol:"tcp", Timeout: 60*time.Duration} srv2, _ := NewServer("locahost", 9000, &conf)  

这段代码算是不错了,大多数情况下,我们可能就止步于此了。但是,对于有洁癖的有追求的程序员来说,他们能看到其中有一点不好的是,Config 并不是必需的,所以,你需要判断是否是 nil 或是 Empty – Config{}这让我们的代码感觉还是有点不是很干净。

Builder模式

如果你是一个Java程序员,熟悉设计模式的一定会很自然地使用上Builder模式。比如如下的代码:

User user = new User.Builder() .name("Hao Chen") .email("[email protected]") .nickname("左耳朵") .build();

仿照上面这个模式,我们可以把上面代码改写成如下的代码(注:下面的代码没有考虑出错处理,其中关于出错处理的更多内容,请参看《Go 编程模式:出错处理》):

//使用一个builder类来做包装 type ServerBuilder struct { Server }   func (sb *ServerBuilder) Create(addr string, port int) *ServerBuilder { sb.Server.Addr = addr sb.Server.Port = port //其它代码设置其它成员的默认值 return sb }   func (sb *ServerBuilder) WithProtocol(protocol string) *ServerBuilder { sb.Server.Protocol = protocol return sb }   func (sb *ServerBuilder) WithMaxConn( maxconn int) *ServerBuilder { sb.Server.MaxConns = maxconn return sb }   func (sb *ServerBuilder) WithTimeOut( timeout time.Duration) *ServerBuilder { sb.Server.Timeout = timeout return sb }   func (sb *ServerBuilder) WithTLS( tls *tls.Config) *ServerBuilder { sb.Server.TLS = tls return sb }   func (sb *ServerBuilder) Build() (Server) { return sb.Server }  

于是就可以以如下的方式来使用了

sb := ServerBuilder{} server, err := sb.Create("127.0.0.1", 8080). WithProtocol("udp"). WithMaxConn(1024). WithTimeOut(30*time.Second). Build()

上面这样的方式也很清楚,不需要额外的Config类,使用链式的函数调用的方式来构造一个对象,只需要多加一个Builder类,这个Builder类似乎有点多余,我们似乎可以直接在Server 上进行这样的 Builder 构造,的确是这样的。但是在处理错误的时候可能就有点麻烦(需要为Server结构增加一个error 成员,破坏了Server结构体的“纯洁”),不如一个包装类更好一些。

如果我们想省掉这个包装的结构体,那么就轮到我们的Functional Options上场了,函数式编程。

Functional Options

首先,我们先定义一个函数类型:

type Option func(*Server)

然后,我们可以使用函数式的方式定义一组如下的函数:

func Protocol(p string) Option { return func(s *Server) { s.Protocol = p } } func Timeout(timeout time.Duration) Option { return func(s *Server) { s.Timeout = timeout } } func MaxConns(maxconns int) Option { return func(s *Server) { s.MaxConns = maxconns } } func TLS(tls *tls.Config) Option { return func(s *Server) { s.TLS = tls } }

上面这组代码传入一个参数,然后返回一个函数,返回的这个函数会设置自己的 Server 参数。例如:

  • 当我们调用其中的一个函数用 MaxConns(30) 时
  • 其返回值是一个 func(s* Server) { s.MaxConns = 30 } 的函数。

这个叫高阶函数。在数学上,就好像这样的数学定义,计算长方形面积的公式为: rect(width, height) = width * height; 这个函数需要两个参数,我们包装一下,就可以变成计算正方形面积的公式:square(width) = rect(width, width) 也就是说,squre(width)返回了另外一个函数,这个函数就是rect(w,h) 只不过他的两个参数是一样的。即:f(x)  = g(x, x)

好了,现在我们再定一个 NewServer()的函数,其中,有一个可变参数 options 其可以传出多个上面上的函数,然后使用一个for-loop来设置我们的 Server 对象。

  func NewServer(addr string, port int, options ...func(*Server)) (*Server, error) {   srv := Server{ Addr: addr, Port: port, Protocol: "tcp", Timeout: 30 * time.Second, MaxConns: 1000, TLS: nil, } for _, option := range options { option(&srv) } //... return &srv, nil }

于是,我们在创建 Server 对象的时候,我们就可以这样来了。

s1, _ := NewServer("localhost", 1024) s2, _ := NewServer("localhost", 2048, Protocol("udp")) s3, _ := NewServer("0.0.0.0", 8080, Timeout(300*time.Second), MaxConns(1000))

怎么样,是不是高度的整洁和优雅?不但解决了使用 Config 对象方式 的需要有一个config参数,但在不需要的时候,是放 nil 还是放 Config{}的选择困难,也不需要引用一个Builder的控制对象,直接使用函数式编程的试,在代码阅读上也很优雅。

所以,以后,大家在要玩类似的代码时,强烈推荐使用Functional Options这种方式,这种方式至少带来了如下的好处:

  • 直觉式的编程
  • 高度的可配置化
  • 很容易维护和扩展
  • 自文档
  • 对于新来的人很容易上手
  • 没有什么令人困惑的事(是nil 还是空)

参考文档

(全文完)

标签:return,编程,FUNCTIONAL,Server,Go,func,GO,sb,OPTIONS
From: https://www.cnblogs.com/gongxianjin/p/17596331.html

相关文章

  • 前端Vue自定义精美商品分类组件category 可用于电商应用分类页面
    随着技术的不断发展,传统的开发方式使得系统的复杂度越来越高。在传统开发过程中,一个小小的改动或者一个小功能的增加可能会导致整体逻辑的修改,造成牵一发而动全身的情况。为了解决这个问题,我们采用了组件化的开发模式。通过组件化开发,可以有效地实现单独开发,单独维护,而且它们之间......
  • go chan阻塞实例
    以下的代码段在执行写入通道的时候会发生阻塞:spaceId2badgeDatasChan:=make(chanmap[int32][]*badgeV1.BadgeData)spaceId2badgeCountChan:=make(chanmap[int32]int32) vargetBadgesTasks[]func() for_,loopSpaceId:=rangespaceIds{ task:=func(sp......
  • 跳表的原理--Golang 实现一个简单跳表
    前言最近在看《Redis设计与实现》这本书,书中简单描述了跳表的性质和数据结构,但对它的具体实现没有多讲。书里对跳表结构的描述是这样的:跳跃表节点:typedefstructzskiplistNode{//后退指针structzskiplistNode*backward;//分值doublescore;//......
  • go 循环变量捕获 陷阱
    以下这样的循环代码,最后会发现spaceId都是同一个!!!! for_,spaceId:=rangespaceIds{ task:=func(){ uc.log.WithContext(ctx).Errorf("SpaceUsecase::GetSpacesuc.badgeClt.GetspaceId:%vstart!!!!",spaceId) } getBadgesTasks=append(getBadgesTasks,task)......
  • OpenHarmony系统解决方案 - 接入多个显示设备卡开机Logo
    问题描述问题环境系统版本:OpenHarmony-3.2-Release问题现象接入多个显示设备后,启动系统偶现卡开机Logo。异常效果:系统卡在开机Logo界面,长时间无法显示开机动画,并且无法进入系统。正常效果:系统启动成功,显示开机动画,开机动画结束后显示锁屏界面。问题原因在窗口子系统中Abstra......
  • could not import go.etcd.io/etcd/clientv3-go
    问题描述今天在封装etcd的时候导包报错:couldnotimportgo.etcd.io/etcd/clientv3(norequiredmoduleprovidespackage"go.etcd.io/etcd/clientv3")问题解决:get:确保下载了client包gogetgo.etcd.io/etcd/clienttidygomodtidy本文由mdnice多平台发布......
  • django 简单文件上传
    通过模型来处理上传的文件¶如果想要在 FileField 上的 Model 保存文件,使用 ModelForm 会让这一过程变得简单。当调用 form.save() 时,文件对象将会被保存在对相应 FileField 的 upload_to 参数所指定的地方:fromdjango.httpimportHttpResponseRedirectfromdja......
  • go操作kafka
    go操作kafkaZooKeeper是一个分布式协调服务,它的主要作用是为分布式系统提供一致性服务,提供的功能包括:配置维护、命名服务、分布式同步、组服务等。Kafka的运行依赖ZooKeeper。目前kafka3.2.0以上版本(kafka_2.13-3.2.0.tgz)内就包含自带的ZooKeeper,因此直接下载Kafka就行。解......
  • 【go语言】3.1.2 接口的定义和实现
    在Go中,接口是一种抽象类型,用来描述其他类型应该有哪些方法。它定义了一组方法,但没有实现。这些方法由其他类型实现。接口的定义接口定义的格式如下:typeInterfaceNameinterface{Method1(param1type1,param2type2)returntype1Method2(param1type1,param2ty......
  • MongoDB数据库的部署和应用
    推荐步骤:在Centos01上部署MongoDB服务器客户端登录验证在centos01的MongoDB配置文件通过配置文件控制MongoDB服务,配置MongoDB身份验证在centos01的MongoDB服务器配置身份验证管理和修改配置文件支持验证在centos01管理MongoDB管理数据,集合批量数据管理实验步骤创建管理MongoDB组和......