golang中的组合多态

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接口,而调用该方法的两个地方传入的是TCPUDP对象指针,这正是朴素上的多态体现,实现了相同接口从而达到一个函数被多个不同类型所调用的现象.

关于结构体方法的接收者类型和使用注意事项:

  1. 接收者类型选择:
  • 结构体方法的接收者可以是值类型(普通对象)或指针类型
  • 建议保持一致:一个结构体的所有方法最好统一使用值接收者或指针接收者
  1. 方法调用灵活性(go对指针的特殊处理):
  • 无论接收者是值类型还是指针类型,都可以通过值或指针来调用方法
  • 关键区别:当接收者为指针类型时,方法内部对结构体的修改会反映到调用者
  1. 接口实现规则:
  • 如果方法使用值接收者,实现接口时可以传值或指针
  • 如果方法使用指针接收者,实现接口时只能传指针

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

  1. 空接口 (interface{}/any) :

    • 使用 eface 表示
    • 不包含方法集信息
    • 空接口有额外的内存开销(两个指针大小)
  • 涉及动态类型检查,比静态类型慢
  1. 非空接口
    • 使用 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

go.dev/wiki/Interf…

[]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{} }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值