Point1: 组合思想
最重要的一点就是组合大于继承,甚至可以夸张到说一句暴论:只有组合没有继承. 直接影响了golang的代码编写风格.
为什么这么说?
是因为Golang的继承就是一种组合,在子类中添加一个父类对象,就算实现了所谓的父类.可是这就是单纯的组合,所以以后别人再和我说用go继承一下,我真的会暴起.
因为之前写的CPP和Java都是会有转型的概念,可以理解为父类指针指向子类对象(将子类对象赋给父类对象),golang没有,这么写就会报错,毕竟是为了解耦合,取消继承是大趋势,Rust好像也是组合优先,这很正常,符合软件开发原则组合优于继承.
go
代码解读
复制代码
type ParentClass struct { // 父结构体的字段 } type ChildClass struct { Parent // 嵌套父结构体 // 子结构体的字段 } func main() { c := ChildClass{} // 创建一个 Child 对象 // p := ParentClass(c) // 强制转型 父类指针指向子类对象 golnag不支持,报错 }
这同样导致另外一个蛋疼的现象: 多态以隐式实现接口的形式出现.
Java的接口是用implements来实现的,Golang是只管定义,实现了方法就算实现接口了.也就是你可能莫名其妙地就实现了一个接口.这就导致用golang开发的时候,智能提示去找相关的接口实现会给你飙到一些风马牛不相及的地方.
这是一个简单的接口实现,网络协议族的接口NetworkProtocol 以TCP UDP通过实现该接口的三个函数来实现网络协议的接口.
golang
代码解读
复制代码
package main import "fmt" // NetworkProtocolIF 网络协议接口,包含三个方法 type NetworkProtocolIF interface { Send(data string) error Receive() (string, error) GetProtocolName() string } // TCP 结构体代表 TCP 协议 type TCP struct { port int } func (t *TCP) Send(data string) error { fmt.Printf("TCP sending data '%s' on port %d\n", data, t.port) return nil // 模拟发送成功 } func (t *TCP) Receive() (string, error) { receivedData := fmt.Sprintf("TCP received data on port %d", t.port) fmt.Println(receivedData) return receivedData, nil // 模拟接收成功 } func (t *TCP) GetProtocolName() string { return "TCP" } // UDP 结构体代表 UDP 协议 type UDP struct { port int } func (u *UDP) Send(data string) error { fmt.Printf("UDP sending data '%s' on port %d\n", data, u.port) return nil // 模拟发送成功 } func (u *UDP) Receive() (string, error) { receivedData := fmt.Sprintf("UDP received data on port %d", u.port) fmt.Println(receivedData) return receivedData, nil // 模拟接收成功 } func (u *UDP) GetProtocolName() string { return "UDP" } func processProtocol(protocol NetworkProtocolIF) { fmt.Println("Processing protocol:", protocol.GetProtocolName()) err := protocol.Send("Hello, network!") if err != nil { fmt.Println("Error sending:", err) } _, err = protocol.Receive() if err != nil { fmt.Println("Error receiving:", err) } fmt.Println() } func main() { processProtocol(&TCP{port: 8080}) processProtocol(&UDP{port: 9000}) }
从上面代码和可以看出,processProtocol(protocol NetworkProtocolIF )
方法的参数是NetworkProtocolIF
接口,而调用该方法的两个地方传入的是TCP
和UDP
对象指针,这正是朴素上的多态体现,实现了相同接口从而达到一个函数被多个不同类型所调用的现象.
关于结构体方法的接收者类型和使用注意事项:
- 接收者类型选择:
- 结构体方法的接收者可以是值类型(普通对象)或指针类型
- 建议保持一致:一个结构体的所有方法最好统一使用值接收者或指针接收者
- 方法调用灵活性(go对指针的特殊处理):
- 无论接收者是值类型还是指针类型,都可以通过值或指针来调用方法
- 关键区别:当接收者为指针类型时,方法内部对结构体的修改会反映到调用者
- 接口实现规则:
- 如果方法使用值接收者,实现接口时可以传值或指针
- 如果方法使用指针接收者,实现接口时只能传指针
Part2 万能类型空接口
记得大学时学C/C++, 爱说的万能类型是nil,万物皆可nil,万物皆可new. Golang似乎也有万能类型就是空接口.
万能类型其实就是空接口(不含任何方法的接口),可以把任何对象赋值给空接口对象(实例化空接口),类似 Java 中的Object
.
golang
代码解读
复制代码
// any 和 interface{} 等价,都可以表示空接口 // `interface{}` 是 Go 语言内置的空接口类型 // `any` 是 Go 1.18 引入的预定义类型别名:`type any = interface{}` 属于语法糖 type any = interface{} var a1 interface{} // 定义一个空接口变量 a1 var a2 any // 定义一个空接口变量 a2
-
空接口 (
interface{}
/any
) :- 使用
eface
表示 - 不包含方法集信息
- 空接口有额外的内存开销(两个指针大小)
- 使用
- 涉及动态类型检查,比静态类型慢
- 非空接口:
- 使用
iface
结构表示 (定义在同一个文件中) - 包含方法表信息
- 使用
空接口的底层实现:
golang
代码解读
复制代码
// src/runtime/runtime2.go type eface struct { _type *_type // 类型指针 data unsafe.Pointer // 数据指针 }
接口可以通过断言判断其动态类型,说明接口中保存了赋值变量的类型,即:_type *_type
接口保存的是变量值,则修改后不影响,接口保存的是指针,则会影响num值
interface{}注意点
golang
代码解读
复制代码
var dataSlice []int = foo() var interfaceSlice []interface{} = dataSlice cannot use dataSlice (type []int) as type []interface { } in assignment
[]interface{}
那么问题是,“当我可以将任何类型分配给 时,为什么不能将任何切片分配给interface{}
?”
-
万能类型是
interface{}
,不是[]interface{}
,[]int
可以赋值给interface{}
,但不能赋值给[]interface{}
-
从内存的角度,假设长度为
N
的[]interface{}
,那么所占空间就是N*2
,因为一个interface{}
中含有两个指针;而对于长度为N
的[]int
,那么所占空间就是N*sizeof(MyType)
interface{}与...语法糖的坑
上述问题,单看还算好理解,一旦和别的掺杂在一起就比较恶心了. ...语法用在函数定义等价于规整为切片,在使用上等价切片打散拆分为单个子元素
golang
代码解读
复制代码
package main import "fmt" func f(a ...int) { fmt.Printf("参数类型: %T, 值: %v\n", a, a) } func g(b ...interface{}) { fmt.Printf("参数类型: %T, 值: %v\n", b, b) } func main() { // 情况1: 直接传递多个int参数 f(1, 2, 3, 4) // 参数类型: []int, 值: [1 2 3 4] // 情况2: 展开int切片 f([]int{1, 2, 3, 4}...) // 参数类型: []int, 值: [1 2 3 4] // 情况3: 尝试展开int8切片 - 会编译错误 // f([]int8{1, 2, 3, 4}...) // 错误: cannot use []int8{...} as []int // 情况4: 使用interface{}可变参数 g(1, "hello", true) // 参数类型: []interface {}, 值: [1 hello true] // 情况5: 展开任意类型切片到interface{} ints := []int{1, 2, 3, 4} g(ints...) // 参数类型: []interface {}, 值: [1 2 3 4] // 情况6: 展开interface{}切片 mix := []interface{}{1, "world", 3.14} g(mix...) // 参数类型: []interface {}, 值: [1 world 3.14] // 情况7: 展开字符串切片 strs := []string{"a", "b", "c"} g(strs...) // 参数类型: []interface {}, 值: [a b c] // 情况8: []int{1, 2, 3, 4}... f([]int{1, 2, 3, 4}...) // 数会转换成[][]interface{}{{1, 2, 3, 4}} // 情况9: []int{1, 2, 3, 4}... f([]int{1, 2, 3, 4}...) 报错 因为被拆分为 int, int ,而不是interfce{},interface{} }