重学Go语言 | Go初始化Struct的几种方式

公众号:程序员读书,欢迎关注

面向对象编程语言最基础的概念就是类(class),不过Go语言并没有类的概念,所以使用Go语言开发时,我们一般会用struct(结构体)来模拟面向对象中的类。

类一般是通过构造方法(constructors)来初始化类的实例(对象)中的属性,不过Gostruct并没有构造方法这种机制,那要怎么样初始化struct类型的实例呢?

下面我们来介绍几种创建struct类型变量的方法。

字面量

创建并初始化一个struct变量最简单直接的方式就是使用struct字面量:

go 复制代码
//app/school.go
package school

type Student struct {
	ID    int
	Name  string
	Score int
	Grade string
}

//main.go
package main
func main() {
	s := &Student{
		ID:    1,
		Name:  "小明",
		Score: 90,
		Grade: "高中二班",
	}
}

不过有时候我们只是需要一个临时struct类型的话,可以使用匿名struct

go 复制代码
func main() {

	s := struct {
		ID    int
		Name  string
		Score int
		Grade string
	}{
		ID: 1, 
        Name: "小明",
        Score: 90,
        Grade: "高中二年级",
	}
}

使用内置new函数

除了字面量外,使用Go内置的new函数也可以创建一个struct变量,不过这种方式需要自己为struct的每个字段赋初始值:

go 复制代码
func main(){
  s := new(Student)
  s.ID = 1
  s.Name = "小明"
  s.Score = 90
  s.Grade = "高中二班"
}

构造函数

前面我们说过Go语言的struct没有构造函数,但是我们可以自定义函数来初始化struct,自定义函数的好处在于:

  • 可以达到复用的目的。
  • 可以起到封装的效果。
  • 可以校验初始化值是否在允许的范围内。

一般我们自定义的构造函数都以New为前缀,比如:

go 复制代码
package school

type student struct {
	ID    int
	Name  string
	Score int
	Grade string
}

func NewStudent(ID int, Name string,Score int Grade string) *Student {
    if ID < 0{
        panic("ID不能小于0")
    }
    
    if Score < 0 || Score > 100{
        panic("Score必须在0~100之间")
    }
    
    s := new(Student)
    s.ID = 1
    s.Name = "小明"
    s.Score = 90
    s.Grade = "高中二班"
    return s
}

package main 

import "app/school"

func main(){
    s := school.NewStudent(1,"小明",90,"高中二班")
}

上面的例子中,我们自定义了NewStudent()函数,之后可以在其他包复用该函数来初始化struct,将Student改为student,这样其他包无法直接访问student结构体,达到了封装的效果,而在NewStudent()函数中,我们也可以验证参数是否合理。

当然自定义构造函数也是必须以New为前缀的,比如标准库os包中,初始化文件句柄时,可以使用Open()Create()OpenFile()等函数来初始化os.File

go 复制代码
//os包的file.go文件的代码
func Open(name string) (*File, error) {
	return OpenFile(name, O_RDONLY, 0)
}

func Create(name string) (*File, error) {
	return OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0666)
}

func OpenFile(name string, flag int, perm FileMode) (*File, error) {
	testlog.Open(name)
	f, err := openFileNolog(name, flag, perm)
	if err != nil {
		return nil, err
	}
	f.appendMode = flag&O_APPEND != 0

	return f, nil
}

支持可变参数的构造函数

在上面的例子中,我们可以通过NewStudent()函数创建一个student类型的变量,但是这个构造函数的参数的顺序与个数是固定的,如果有多个地方调用NewStudent()函数,此时在该函数新增一个参数时,那么所有调用到该函数的代码都必须调整。

我们可以再优化一下我们的构造函数,使用其参数的个数与顺序是可变的:

go 复制代码
package school

type student struct {
	ID    int
	Name  string
	Score int
	Grade string
}

type StudentOptionFunc func(*student)

func WithID(id int) StudentOptionFunc {
	return func(s *student) {
		s.ID = id
	}
}

func WithName(name string) StudentOptionFunc {
	return func(s *student) {
		s.Name = name
	}
}

func WithScore(score int) StudentOptionFunc {
	return func(s *student) {
		s.Score = score
	}
}

func WithGrade(grade string) StudentOptionFunc {
	return func(s *student) {
		s.Grade = grade
	}
}

func NewStudent(opts ...StudentOptionFunc) *student {
	s := &student{}
	for _, opt := range opts {
		opt(s)
	}
	return s
}

上面的例子中,构造函数NewStudent()的参数是不定长StudentOptionFunc类型的函数,可以看作是一个装了多个函数的切片,在NewStudent()函数内部遍历这个切片,通过切片中的每个函数来初始化自己的字段。

接下来在调用中,我们就可以不受参数个数与顺序的影响来调用NewStudent()函数了:

go 复制代码
package main

import (
	"app/school"
	"fmt"
)

func main() {
	s1 := school.NewStudent(
		school.WithName("小明"), 
        school.WithScore(90),
        school.WithID(1),
        school.WithGrade(""),
	)	
    
    s2 := school.NewStudent(
		school.WithName("小花"), 
        school.WithScore(90),
	)	
}

折中方案

上面演示了两种自定义构造函数,一种初始化参数个数与顺序完全固定,这种方式太死板了,而一种则是可以自由传入参数的顺序与个数,这种方式又太自由了,因为我们可以想一个折中的方案,即把两种方式结合起来:

go 复制代码
func NewStudent(id int, Name string, opts ...StudentOptionFunc) *student {
	s := &student{ID: id, Name: Name}
	for _, opt := range opts {
		opt(s)
	}
	return s
}

小结

上面的几种介绍的几种初始化struct的方式并没有好坏之分,选择你喜欢的方式去初始化struct变量即可。

相关推荐
David爱编程几秒前
Java 守护线程 vs 用户线程:一文彻底讲透区别与应用
java·后端
小奏技术17 分钟前
国内APP的隐私进步,从一个“营销授权”弹窗说起
后端·产品
小研说技术36 分钟前
Spring AI存储向量数据
后端
苏三的开发日记36 分钟前
jenkins部署ruoyi后台记录(jenkins与ruoyi后台处于同一台服务器)
后端
苏三的开发日记37 分钟前
jenkins部署ruoyi后台记录(jenkins与ruoyi后台不在同一服务器)
后端
陈三一42 分钟前
MyBatis OGNL 表达式避坑指南
后端·mybatis
whitepure43 分钟前
万字详解JVM
java·jvm·后端
我崽不熬夜1 小时前
Java的条件语句与循环语句:如何高效编写你的程序逻辑?
java·后端·java ee
我崽不熬夜1 小时前
Java中的String、StringBuilder、StringBuffer:究竟该选哪个?
java·后端·java ee
我崽不熬夜2 小时前
Java中的基本数据类型和包装类:你了解它们的区别吗?
java·后端·java ee