Go 语言使用结构体和结构体成员来描述真实世界的实体和实体对应的各种属性,结构体是类型中带有成员的复合类型,属于值类型。
Go 语言中的类型可以被实例化,使用new()
或&
可以实例化该类型、获取实例的指针。
结构体成员是由一系列的成员变量构成,这些成员变量也被称为字段。字段有以下特性:
同样对于可见性,结构体的名称、结构体字段、结构体方法的名称首字母大小写规则和方法、变量名称规则相同。
使用关键字type
可以将各种基本类型定义为自定义类型,基本类型包括int、string、bool等。
结构体是一种复合的基本类型,通过type
定义为自定义类型后,使结构体更便于使用。
type structName struct {
filed1Name filed1Type
filed2Name filed2Type
filed3Name filed3Type
}
structName
:表示自定义结构体的名称,在同一个包内不能重复。struct{}
:表示结构体类型,type structName struct{}
可以理解为将 struct{}
结构体定义为structName
的类型。filed1Name、filed2Name
:表示结构体字段名,结构体中的字段名必须唯一。filed1Type、filed2Type
:表示结构体各个字段的类型。type Stu struct {
name string
age int
}
由于结构体属于基本数据类型(值类型),所以在声明时,系统会自动对结构体以及结构体字段赋零值
使用var
关键字
var s Stu
fmt.Println(s) // main.Stu{name:"", age:0}
sp := new(Stu)
s := *sp
fmt.Printf("%#v\n", s) // main.Stu{name:"", age:0}
可以在大括号中以键值对或成员声明顺序的方式进行赋值
s1 := Stu{}
fmt.Printf("%#v\n", s1) // main.Stu{name:"", age:0}
s2 := Stu{"lucy", 18}
fmt.Printf("%#v\n", s2) // main.Stu{name:"lucy", age:18}
s3 := Stu{name: "tom", age: 25}
fmt.Printf("%#v\n", s3) // main.Stu{name:"tom", age:25}
// 这种方法也可以直接获取结构体指针
s := &Stu{"lucy", 18}
fmt.Printf("%T\n", s) // *main.Stu
fmt.Printf("%#v\n", s) // &main.Stu{name:"lucy", age:18}
结构体变量和结构体指针都可以使用操作符.
对进行读写操作
package main
import "fmt"
type Stu struct {
name string
age int
}
func main() {
s := Stu{"lucy", 18}
s.name = "tom"
fmt.Println(s) // {tom 18}
sp := &s
sp.name = "lily"
fmt.Println(s) // {lily 18}
}
在Go语言中,没有类的概念但是可以给结构体、自定义类型定义方法。Go 方法是作用在接收者(receiver)上的一个函数,接受者就是结构体、自定义类型的实例,类似于其他语言中的this
或self
。
使用接收者,就可以直接在实例后使用.
来调用方法,并且操作实例
非本地类型不可定义方法,也就是不可以给别的包的类型定义方法。
package main
import (
"fmt"
)
type Stu struct {
name string
age int
}
// 定义结构体Stu的方法
func (s *Stu) ShowInfo() {
fmt.Printf("the user name is %v, age is %d\n", s.name, s.age)
}
func main() {
s := Stu{"lucy", 18}
s.ShowInfo() // the user name is lucy, age is 18
}
s
,Connector 类型的接收器变量应该命名为c
等。在Java里面,实例的信息字符串一般由toString
方法提供,在go里面,是由String() string
函数提供,其原理也是实现了接口的方法
package main
import (
"fmt"
)
type Stu struct {
name string
age int
}
func (s Stu) String() string {
return fmt.Sprintf("the user name is %v, age is %d\n", s.name, s.age)
}
func main() {
s := Stu{"lucy", 18}
fmt.Println(s) // the user name is lucy, age is 18
fmt.Println(&s) // the user name is lucy, age is 18
}
结构体可以包含一个或多个匿名(或内嵌)字段,即这些字段没有显式的名字,只有字段的类型是必须的,此时类型也就是字段的名字。
package main
import "fmt"
type Stu struct {
int
string
}
func main() {
s := Stu{18, "lucy"}
fmt.Println(s.int, s.string) // 18 lucy
}
Go语言中的继承是通过内嵌或组合来实现的
同样地结构体也是一种数据类型,所以它也可以作为一个匿名字段来使用。内层结构体被简单的插入或者内嵌进外层结构体,外层结构体实例可以直接访问内嵌结构体成员。这个简单的“继承”机制提供了一种方式,使得可以从另外一个或一些类型继承部分或全部实现。
内嵌结构体可以对外层的结构体进行功能增强,外层结构体可以直接调用内层的方法
package main
import "fmt"
type Father struct {
name string
age int
}
type Chil struct {
id int
Father
}
func main() {
c := Chil{111, Father{"lucy", 18}}
fmt.Printf("id:%d,name:%s,age:%d", c.id, c.name, c.age)
}
Go语言的结构体内嵌有如下特性:
特性 | 传统面向对象语言 | Go语言 |
---|---|---|
类 | 有类的概念,作为对象的蓝图 | 没有类,使用结构体定义数据结构 |
构造函数 | 专门的构造方法 | 使用普通函数创建和初始化结构体 |
继承 | 通过类继承实现代码复用和多态 | 没有继承,使用组合和接口 |
多态 | 通过继承和方法重写实现 | 通过接口实现 |
封装 | 通过访问修饰符控制可见性 | 通过大小写控制可见性 |
方法重载 | 支持同名不同参数的方法 | 不支持方法重载 |
异常处理 | 通常使用try-catch机制 | 使用多返回值和错误处理 |
范型(Go 1.18前) | 大多支持 | 不支持,使用接口和反射 |
Go没有专门的构造函数,但可以创建返回初始化结构体的函数,这是一种常见的惯例:
构造函数的优势:
// NewPerson作为Person的构造函数
func NewPerson(firstName, lastName string, age int, address string) *Person {
// 可以在这里进行参数验证
if age < 0 {
age = 0
}
return &Person{
FirstName: firstName,
LastName: lastName,
Age: age,
Address: address,
}
}
func main() {
// 使用构造函数创建实例
p := NewPerson("张", "三", 30, "北京市海淀区")
fmt.Printf("%+v\n", *p)
}
在Go中实现面向对象编程时,应该遵循一些最佳实践,确保代码的可读性、可维护性和性能。
优先使用组合:优先使用组合而非模拟继承。
// 不推荐:模拟继承
type Animal struct {
Name string
}
func (a Animal) Speak() string {
return "某种声音"
}
type Dog struct {
Animal // 试图模拟继承
Breed string
}
// 推荐:明确组合
type Animal struct {
Name string
}
func (a Animal) Speak() string {
return "某种声音"
}
type Dog struct {
Animal Animal // 清晰表明这是组合
Breed string
}
func (d Dog) Speak() string {
return "汪汪"
}
接口应该小而精确:定义最小可行的接口。
// 不推荐:过大的接口
type FileProcessor interface {
Open(filename string) error
Read() ([]byte, error)
Process() error
Write(data []byte) error
Close() error
}
// 推荐:小而专注的接口
type Reader interface {
Read() ([]byte, error)
}
type Writer interface {
Write(data []byte) error
}
type Processor interface {
Process() error
}
// 可以组合使用
type FileHandler struct {
// ...
}
func (f *FileHandler) Read() ([]byte, error) {
// 实现读取
}
func (f *FileHandler) Write(data []byte) error {
// 实现写入
}
func (f *FileHandler) Process() error {
// 实现处理
}
避免接口污染:不要在结构体上定义不需要的方法仅为了满足接口。
// 不推荐:为了满足接口添加不必要的方法
type Logger interface {
Log(message string)
LogError(err error)
LogWarning(message string)
}
type SimpleLogger struct{}
func (l SimpleLogger) Log(message string) {
fmt.Println(message)
}
// 这些方法仅为了满足接口
func (l SimpleLogger) LogError(err error) {
l.Log(err.Error()) // 只是转发到Log
}
func (l SimpleLogger) LogWarning(message string) {
l.Log("WARNING: " + message) // 只是转发到Log
}
// 推荐:使用适配器模式
type Logger interface {
Log(message string)
LogError(err error)
LogWarning(message string)
}
type SimpleLogger struct{}
func (l SimpleLogger) Log(message string) {
fmt.Println(message)
}
// 使用适配器满足复杂接口
type LoggerAdapter struct {
logger SimpleLogger
}
func (a LoggerAdapter) Log(message string) {
a.logger.Log(message)
}
func (a LoggerAdapter) LogError(err error) {
a.logger.Log(err.Error())
}
func (a LoggerAdapter) LogWarning(message string) {
a.logger.Log("WARNING: " + message)
}
要在Go中成功应用面向对象编程,需要调整思维方式: