Go 其五 到底是不是面向对象语言 -- 封装数据和行为, 接口, 自定义类型
关于Go是不是面向对象语言其实有很多争论,关于给出的解释是:Yes and no.
封装数据和行为
结构体定义
type Employee struct { Id string Name string Age int }
实例创建及初始化
e := Employee{"0", "Bob", 20} e1 := Employee{Name: "Mike", Age: 30} e2 := new(Employee) //注意这里返回的引用/指针, 相当于 e:= &Employee{} e2.Id = "2" //与其他主要编程语言的差异:通过实例的指针访问成员不需要使用-> e2.Age = 22 e2.Name = "Rose"
行为(方法)的定义
//第一种定义方式在实例对应方法被调用时,实例的成员会进行值复制 func (e Employee) String() string { return fmt.Sprintf("ID:%s-Name:%s-Age:%d", e.Id, e.Name, e.Age) } //第二种通常情况下为了避免内存拷贝我们使用第二种定义方式 func (e *Employee) String() string { return fmt.Sprintf("ID:%s/Name:%s/Age:%d", e.Id, e.Name, e.Age) }
Duck Type式接口实现
接口定义
type Programmer interface { WriteHelloWorld() Code }
接口实现
type GoProgrammer struct { } func (p *GoProgrammer) WriteHelloWorld() Code { return "fmt.Println(\"Hello World!\")" }
Go接口
与其他主要编程语言的差异
- 接口为非入侵性的,实现不依赖于接口的定义
- 所以接口的定义可以包含在接口使用者包内
接口变量
var prog Coder = &GoProgrammer{} // 以上prog是接口Coder的一个变量
当prog被初始化之后它有两部分
prog
type GoProgrammer stuct { //类型 类型 --> } 数据 --> &GoProgrammer{} //具体的实现
自定义类型
举例:
1. type IntConvertionFn func(n int) int
2. type MyPoint int
Go语言中接口的实现是不依赖于接口的定义的,是采用DockType的方式
接口
以上是笔者在进行Go语言面向对象方面知识所记录的笔记,可能会有一些凌乱。如果你和我的技术栈类似,可能会对Go中的接口定义以及实现,Duck Type等部分内容感到新奇。可以看看接口这部分的代码:
package interface_test import ( "testing" ) type Programmer interface{ WriteHelloWorld() string } type GoProgrammer struct{ } func (g *GoProgrammer) WriteHelloWorld() string{ return "fmt.Println(\"Hello World!\")" } /*注意这里,其实p定义的是接口Programmer,而p = new(GoProgrammer) 是将接口Programmer的具体实现'GoProgrammer'作为p的实例 这里没有使用传统的【接口定义,接口实现继承自接口定义,具体使用的地方利用接口定义通过容器或别的方式获取接口实现】的传统方法 而是使用了DuckType.所以DuckType就是指,这个鸟虽然我不知道是什么,但是看起来脚上有蹼,扁嘴,像是鸭子,那么将当它是鸭子。(2333,这是老师的原话) 对应上面就是接口GoProgrammer所对应的方法‘WriteHelloWorld’与Programmer中定义的方法看起来是一样的,那么我们就当GoProgrammer是Programmer的具体实现 */ func TestClinet(t *testing.T){ var p Programmer p = new(GoProgrammer) t.Log(p.WriteHelloWorld()) }
如上述代码,如果是以"C#"之类的语言来看,其实我们在TestClient中是定义了一个Programmer接口的变量p,而p的具体实现则是 “类” GoProgrammer 。但我们观察 “类” GoProgrammer,其实并不像C#中那样要继承自Programmer接口,只是这个方法定义的相同。这就是所谓的“Duck Type”.
自定义类型
而关于自定义类型,其实是可以自定义出一些“复杂”的类型,并利用这写类型来简化一些功能的实现.
package customer_type import ( "testing" "fmt" "time" ) type IntConv func(op int) int //通过自定义类型,让程序有更好的可读性,这里自定义了IntCov类型,这个类型是入参为一个int,返回一个int的函数 func timeSpent(inner func(op int) int) IntConv { return func(n int) int { start := time.Now() ret := inner(n) fmt.Println("time spent:", time.Since(start).Seconds()) return ret } } func slowFun(op int) int{ time.Sleep(time.Second *1) return op } func TestFn(t *testing.T) { // 计算函数运行时间 tsSF := timeSpent(slowFun) t.Log(tsSF(10)) }
如上述代码,我们的自定义类型IntConv其实是一个参数为int,返回值为int的函数。而timeSpent方法的返回值是IntConv类型,作用是计算某个函数的运行时间。如果你仔细观察,其实会发现timeSpent不只返回值,参数其实也是IntConv类型,因此这个方法改写成如下是完全OK的。
func timeSpent(inner IntConv) IntConv { return func(n int) int { start := time.Now() ret := inner(n) fmt.Println("time spent:", time.Since(start).Seconds()) return ret } }
而关于行为方法的定义,其实没什么好说的,只是要注意两种方式的不同,为了避免内存复制,尽量使用 " Employee"这种方式.
package encapsulation import ( "testing" "fmt" "unsafe" ) type Employee struct { Id string Name string Age int } func TestCreateEmployeeObj(t *testing.T){ e := Employee{"0", "Bob", 20} e1 := Employee{Name: "Mike", Age: 30} e2 := new(Employee) //注意这里返回的引用/指针, 相当于 e:= &Employee{} e2.Id = "2" //与其他主要编程语言的差异:通过实例的指针访问成员不需要使用-> e2.Age = 22 e2.Name = "Rose" t.Log(e) t.Log(e1) t.Log(e1.Id) t.Log(e2) //%T 代表输出类型 t.Logf("e is %T", e) //输出 e is encapsulation.Employee t.Logf("e2 is %T", e2) //输出 e2 is *encapsulation.Employee } //第一种定义方式在市里对应方法被调用时,实例的成员会进行值复制 func (e Employee) String() string { fmt.Printf("Address is %x\n", unsafe.Pointer(&e.Name)) return fmt.Sprintf("ID:%s-Name:%s-Age:%d", e.Id, e.Name, e.Age) } //第二种通常情况下为了避免内存拷贝我们使用第二种定义方式 func (e *Employee) String2() string { fmt.Printf("Address is %x\n", unsafe.Pointer(&e.Name)) return fmt.Sprintf("ID:%s/Name:%s/Age:%d", e.Id, e.Name, e.Age) } func TestStructOperations(t *testing.T) { e := Employee{"0", "Bob", 20} t.Log(e.String()) fmt.Printf("Address is %x\n", unsafe.Pointer(&e.Name)) /* 输出为: Address is c000064520 TestStructOperations: encap_test.go:45: ID:0-Name:Bob-Age:20 Address is c0000644f0 */ t.Log(e.String2()) fmt.Printf("Address is %x\n", unsafe.Pointer(&e.Name)) /* 输出为: Address is c0000644f0 TestStructOperations: encap_test.go:48: ID:0/Name:Bob/Age:20 Address is c0000644f0 */ //结论,第一种方式(func (e Employee) String() string)会有更大的内存开销,因为是把结构的数据copy了一份,而第二种方式(func (e *Employee) String2() string)引用了相同的地址。 }