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 就使用了选项模式,感兴趣的同学可以看下其源码的实现思想其实和这里的示例程序如出一辙。

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

相关文章
|
1月前
|
设计模式 安全 Java
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
|
3月前
|
设计模式 数据库连接 PHP
PHP中的设计模式:提升代码的可维护性与扩展性在软件开发过程中,设计模式是开发者们经常用到的工具之一。它们提供了经过验证的解决方案,可以帮助我们解决常见的软件设计问题。本文将介绍PHP中常用的设计模式,以及如何利用这些模式来提高代码的可维护性和扩展性。我们将从基础的设计模式入手,逐步深入到更复杂的应用场景。通过实际案例分析,读者可以更好地理解如何在PHP开发中应用这些设计模式,从而写出更加高效、灵活和易于维护的代码。
本文探讨了PHP中常用的设计模式及其在实际项目中的应用。内容涵盖设计模式的基本概念、分类和具体使用场景,重点介绍了单例模式、工厂模式和观察者模式等常见模式。通过具体的代码示例,展示了如何在PHP项目中有效利用设计模式来提升代码的可维护性和扩展性。文章还讨论了设计模式的选择原则和注意事项,帮助开发者在不同情境下做出最佳决策。
|
1月前
|
设计模式 开发者 Python
Python编程中的设计模式:工厂方法模式###
本文深入浅出地探讨了Python编程中的一种重要设计模式——工厂方法模式。通过具体案例和代码示例,我们将了解工厂方法模式的定义、应用场景、实现步骤以及其优势与潜在缺点。无论你是Python新手还是有经验的开发者,都能从本文中获得关于如何在实际项目中有效应用工厂方法模式的启发。 ###
|
1月前
|
设计模式 安全 Java
Kotlin - 改良设计模式 - 构建者模式
Kotlin - 改良设计模式 - 构建者模式
|
1月前
|
设计模式 安全 Java
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
42 1
|
2月前
|
设计模式 Java Kotlin
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
本教程详细讲解Kotlin语法,适合希望深入了解Kotlin的开发者。对于快速学习Kotlin语法,推荐查看“简洁”系列教程。本文重点介绍了构建者模式在Kotlin中的应用与改良,包括如何使用具名可选参数简化复杂对象的创建过程,以及如何在初始化代码块中对参数进行约束和校验。
32 3
|
3月前
|
设计模式 算法 安全
设计模式——模板模式
模板方法模式、钩子方法、Spring源码AbstractApplicationContext类用到的模板方法
设计模式——模板模式
|
3月前
|
设计模式 数据库连接 PHP
PHP中的设计模式:如何提高代码的可维护性与扩展性在软件开发领域,PHP 是一种广泛使用的服务器端脚本语言。随着项目规模的扩大和复杂性的增加,保持代码的可维护性和可扩展性变得越来越重要。本文将探讨 PHP 中的设计模式,并通过实例展示如何应用这些模式来提高代码质量。
设计模式是经过验证的解决软件设计问题的方法。它们不是具体的代码,而是一种编码和设计经验的总结。在PHP开发中,合理地使用设计模式可以显著提高代码的可维护性、复用性和扩展性。本文将介绍几种常见的设计模式,包括单例模式、工厂模式和观察者模式,并通过具体的例子展示如何在PHP项目中应用这些模式。
|
3月前
|
设计模式 Java Spring
spring源码设计模式分析-代理设计模式(二)
spring源码设计模式分析-代理设计模式(二)
|
2月前
|
设计模式 安全 Java
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
Kotlin教程笔记(51) - 改良设计模式 - 构建者模式
43 0
下一篇
DataWorks