Golang 常见设计模式之选项模式

简介: Golang 常见设计模式之选项模式

在程序开发中,有些场景是我们经常会遇到的,软件行业的先行者们帮我们总结了一些解决常见场景编码问题的最佳实践,于是就有了设计模式。选项模式在 Go 语言开发中会经常用到,所以今天我们来介绍一下选项模式的应用。

熟悉 Python 开发的同学都知道,Python 有默认参数的存在,使得我们在实例化一个对象的时候,可以根据需要来选择性的覆盖某些默认参数,以此来决定如何实例化对象。当一个对象有多个默认参数时,这个特性非常好用,能够优雅的简化代码。

而 Go 语言从语法上是不支持默认参数的,所以为了实现既能通过默认参数创建对象,又能通过传递自定义参数创建对象,我们就需要通过一些编程技巧来实现。

通过多构造函数实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package main
import"fmt"
const (
	defaultAddr = "127.0.0.1"
	defaultPort = 8000
)
type Server struct {
	Addr string
	Port int
}
funcNewServer() *Server {
return &Server{
		Addr: defaultAddr,
		Port: defaultPort,
	}
}
funcNewServerWithOptions(addr string, port int) *Server {
return &Server{
		Addr: addr,
		Port: port,
	}
}
funcmain() {
	s1 := NewServer()
	s2 := NewServerWithOptions("localhost", 8001)
	fmt.Println(s1)  // &{127.0.0.1 8000}
	fmt.Println(s2)  // &{localhost 8001}
}

这里我们为 Server 结构体实现了两个构造函数,其中 NewServer 无需传递参数即可直接返回 Server 对象,NewServerWithOptions 则需要传递 addrport 两个参数来构造 Server 对象。当我们无需对 Server 进行定制,通过默认参数创建的对象即可满足需求时,我们可以使用 NewServer 来生成对象(s1),而当我们需要对其进行定制时,则可以使用 NewServerWithOptions 来生成对象(s2)。

通过默认参数选项实现

另外一种实现默认参数的方案是,我们可以为要生成的结构体对象定义一个选项结构体,用来生成要创建对象的默认参数,代码实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package main
import"fmt"
const (
	defaultAddr = "127.0.0.1"
	defaultPort = 8000
)
type Server struct {
	Addr string
	Port int
}
type ServerOptions struct {
	Addr string
	Port int
}
funcNewServerOptions() *ServerOptions {
return &ServerOptions{
		Addr: defaultAddr,
		Port: defaultPort,
	}
}
funcNewServerWithOptions(opts *ServerOptions) *Server {
return &Server{
		Addr: opts.Addr,
		Port: opts.Port,
	}
}
funcmain() {
	s1 := NewServerWithOptions(NewServerOptions())
	s2 := NewServerWithOptions(&ServerOptions{
		Addr: "localhost",
		Port: 8001,
	})
	fmt.Println(s1)  // &{127.0.0.1 8000}
	fmt.Println(s2)  // &{localhost 8001}
}

我们为 Server 结构体专门实现了一个 ServerOptions 用来生成默认参数,调用 NewServerOptions 函数即可获得默认参数配置,构造函数 NewServerWithOptions 接收一个 *ServerOptions 类型作为参数,所以我们可以直接将调用 NewServerOptions 函数的返回值传递给 NewServerWithOptions 来实现通过默认参数生成对象(s1),也可以通过手动构造 ServerOptions 配置来生成定制对象(s2)。

通过选项模式实现

以上两种方式虽然都能够完成功能,但实现上却都不够优雅,接下来我们一起来看下如何通过选项模式更优雅的解决这个问题,代码实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
package main
import"fmt"
const (
	defaultAddr = "127.0.0.1"
	defaultPort = 8000
)
type Server struct {
	Addr string
	Port int
}
type ServerOptions struct {
	Addr string
	Port int
}
type ServerOption interface {
	apply(*ServerOptions)
}
type FuncServerOption struct {
	f func(*ServerOptions)
}
func(fo FuncServerOption)apply(option *ServerOptions) {
	fo.f(option)
}
funcWithAddr(addr string)ServerOption {
return FuncServerOption{
		f: func(options *ServerOptions) {
			options.Addr = addr
		},
	}
}
funcWithPort(port int)ServerOption {
return FuncServerOption{
		f: func(options *ServerOptions) {
			options.Port = port
		},
	}
}
funcNewServer(opts ...ServerOption) *Server {
	options := ServerOptions{
		Addr: defaultAddr,
		Port: defaultPort,
	}
for _, opt := range opts {
		opt.apply(&options)
	}
return &Server{
		Addr: options.Addr,
		Port: options.Port,
	}
}
funcmain() {
	s1 := NewServer()
	s2 := NewServer(WithAddr("localhost"), WithPort(8001))
	s3 := NewServer(WithPort(8001))
	fmt.Println(s1)  // &{127.0.0.1 8000}
	fmt.Println(s2)  // &{localhost 8001}
	fmt.Println(s3)  // &{127.0.0.1 8001}
}

乍一看,我们的代码复杂了很多,但其实都是定义上的复杂,调用构造函数生成对象的代码复杂度是没有改变的。

在这里我们定义了 ServerOptions 结构体用来配置默认参数,因为这里 AddrPort 都有默认参数,所以 ServerOptions 的定义和 Server 定义是一样的,但有一定复杂性的结构体中可能会有些参数没有默认参数,必须让用户来配置,这时 ServerOptions 的字段就会少一些,大家可以按需定义。

同时,我们还定义了一个 ServerOption 接口和实现了此接口的 FuncServerOption 结构体,它们的作用是让我们能够通过 apply 方法为 ServerOptions 结构体单独配置某项参数。

我们可以分别为每个默认参数都定义一个 WithXXX 函数用来配置参数,如这里定义的 WithAddrWithPort ,这样用户就可以通过调用 WithXXX 函数来定制需要覆盖的默认参数。

此时默认参数定义在构造函数 NewServer 中,构造函数的接收一个不定长参数,类型为 ServerOption,在构造函数内部通过一个 for 循环调用每个传进来的 ServerOption 对象的 apply 方法,将用户配置的参数依次赋值给构造函数内部的默认参数对象 options 中,以此来替换默认参数,for 循环执行完成后,得到的 options 对象将是最终配置,将其属性依次赋值给 Server 即可生成新的对象。

总结

通过 s2s3 的打印结果可以发现,使用选项模式实现的构造函数更加灵活,相较于前两种实现,选项模式中我们可以自由的更改其中任意一项或多项默认配置。

虽然选项模式确实会多写一些代码,但多数情况下这是值得的,Google 的 gRPC 框架 Go 语言实现中创建 gRPC server 的构造函数 NewServer 就使用了选项模式,感兴趣的同学可以看下其源码的实现思想其实和这里的示例程序如出一辙。

希望今天的分享能够给你带来一点帮助。

相关文章
|
7天前
|
设计模式 算法 安全
设计模式——模板模式
模板方法模式、钩子方法、Spring源码AbstractApplicationContext类用到的模板方法
设计模式——模板模式
|
1月前
|
设计模式
设计模式-单一职责模式
设计模式-单一职责模式
|
1月前
|
设计模式 XML 存储
【二】设计模式~~~创建型模式~~~工厂方法模式(Java)
文章详细介绍了工厂方法模式(Factory Method Pattern),这是一种创建型设计模式,用于将对象的创建过程委托给多个工厂子类中的某一个,以实现对象创建的封装和扩展性。文章通过日志记录器的实例,展示了工厂方法模式的结构、角色、时序图、代码实现、优点、缺点以及适用环境,并探讨了如何通过配置文件和Java反射机制实现工厂的动态创建。
【二】设计模式~~~创建型模式~~~工厂方法模式(Java)
|
1月前
|
设计模式 XML Java
【一】设计模式~~~创建型模式~~~简单工厂模式(Java)
文章详细介绍了简单工厂模式(Simple Factory Pattern),这是一种创建型设计模式,用于根据输入参数的不同返回不同类的实例,而客户端不需要知道具体类名。文章通过图表类的实例,展示了简单工厂模式的结构、时序图、代码实现、优缺点以及适用环境,并提供了Java代码示例和扩展应用,如通过配置文件读取参数来实现对象的创建。
【一】设计模式~~~创建型模式~~~简单工厂模式(Java)
|
1月前
|
设计模式 uml C语言
设计模式----------工厂模式之简单工厂模式(创建型)
这篇文章详细介绍了简单工厂模式,包括其定义、应用场景、UML类图、通用代码实现、运行结果、实际应用例子,以及如何通过反射机制实现对象创建,从而提高代码的扩展性和维护性。
设计模式----------工厂模式之简单工厂模式(创建型)
|
1月前
|
设计模式 uml
设计模式-------------工厂模式之工厂方法模式(创建型)
工厂方法模式是一种创建型设计模式,它通过定义一个用于创建对象的接口,让子类决定实例化哪一个类,从而实现类的实例化推迟到子类中进行,提高了系统的灵活性和可扩展性。
|
1月前
|
设计模式 测试技术 Go
[设计模式]创建型模式-简单工厂模式
[设计模式]创建型模式-简单工厂模式
|
2月前
|
设计模式 算法 Java
跟着GPT学设计模式之模板模式
模板模式是一种行为型设计模式,它定义了一个操作中的算法骨架,将一些步骤的具体实现延迟到子类中。该模式使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
44 6
|
1月前
|
设计模式 人工智能 达摩院
设计模式的基础问题之模板模式在软件开发中的优势是什么
设计模式的基础问题之模板模式在软件开发中的优势是什么
|
1月前
|
设计模式 项目管理
设计模式的基础问题之生成器模式在项目管理应用的问题如何解决
设计模式的基础问题之生成器模式在项目管理应用的问题如何解决