Go语言入门到实战——00主目录
在上一讲中我们学习了Go语言的面向对象的知识。
一.接口与依赖
首先看上图,A是一个接口,而AImpl则是这个接口的实现,AClient则是创建了AImpl对象并且调用了接口的方法,那么出现了上面的依赖情况,现在假设我们把A和AClient放在一个包里面,而AImpl放在另外一个包里面,那么这个时候会出现循环依赖的问题,那么我们一般就要注意不能这样建包,一般接口是单独放在一个包的。
二.Duck Type式接口实现
package test
import "testing"
type Programmer interface {
WriteHelloWorld() string
}
type GoProgrammer struct {
}
func (g *GoProgrammer) WriteHelloWorld() string {
return "I am Jack"
}
func TestClient(t *testing.T) {
p := new(GoProgrammer)//new一个实例对象,返回指针类型
t.Log(p.WriteHelloWorld())
}
上面可能看起来会比较奇怪就是感觉好像并没有实现接口的意思,其实这是Go语言的Duck Type类型做法,
就是我们定义了接口Programmer,那么结构体GoProgrammer的方法和Programmer的方法一模一样(参数
,函数名,返回值),这种看起来很形象的像鸭子一样的东西,不知道是什么鸟,但是很像,这就是Duck Type,
这样我们就认为实现了接口,这里·就给我们一个启发,就是以后我们再编程写结构体,想加个接口进行抽象化,
这时也不需要将原来的结构体加个啥implments啥的,比较形象方便
这里我们做个总结:
1.go接口是非入侵性质的,实现不依赖于接口的定义
2.所以接口的定义可以包含在接口使用者里面(就是本文第一小节讲解的Interface和Client放在一个包),
这也是Go和主流语言的一个区别
三.接口变量与自定义变量
1.接口变量
var prog Programmer = &GoProgrammer{}//Programmer是接口类型,GoProgrammer是其实现
2.自定义类型(举例说明)
type MyInt int64//自定义变量类型
type MyFunc func(int,int) string//自定义函数类型
四.扩展和复用(Go不支持继承)
这一节主要介绍下Go语言是不支持继承的,主要做复用(其他语言的常见做法)和Go语言的内嵌方式来
尝试继承。
1.复用
//采用复用的方式来模拟继承,Pet是父类,Dog是子类
package test
import (
"fmt"
"testing"
)
type Pet struct {
}
func (p *Pet) Speak() {
fmt.Println("...")
}
func (p *Pet) SpeakTo(host string) {
p.Speak()
fmt.Println(host)
}
type Dog struct {
p *Pet
}
//模拟继承,那么这里调用父类方法
func (d *Dog) Speak() {
d.p.Speak()
}
//模拟继承,那么这里调用父类方法
func (d *Dog) SpeakTo(host string) {
d.p.SpeakTo(host)
}
func TestInhenrit(t *testing.T) {
d := new(Dog)
d.SpeakTo("Jack")
}
//在上面的基础上我们使用重载,将Dog的Speak方法重载来实现,但是结果输出并不是"Wang,还是"..."
//说明复用这个方案没有办法实现继承
package test
import (
"fmt"
"testing"
)
type Pet struct {
}
func (p *Pet) Speak() {
fmt.Println("...")
}
func (p *Pet) SpeakTo(host string) {
p.Speak()
fmt.Println(host)
}
type Dog struct {
p *Pet
}
//模拟继承,那么这里调用父类方法
func (d *Dog) Speak() {
fmt.Println("Wang")
}
//模拟继承,那么这里调用父类方法
func (d *Dog) SpeakTo(host string) {
d.p.SpeakTo(host)
}
func TestInhenrit(t *testing.T) {
d := new(Dog)
d.SpeakTo("Jack")
}
2.内嵌
package test
//内嵌方法:我们没有写Dog的方法,就可以直接调用对于的Pet的方法了,这雨复用是不同的
import (
"fmt"
"testing"
)
type Pet struct {
}
func (p *Pet) Speak() {
fmt.Println("...")
}
func (p *Pet) SpeakTo(host string) {
p.Speak()
fmt.Println(host)
}
type Dog struct {
Pet//内嵌方法,我们只需要写一个类型在这就可以了
}
func TestInhenrit(t *testing.T) {
d := new(Dog)
d.SpeakTo("Jack")
}
下面我们重载方法看看:
//然而重写Pet方法结果依然还是没有改变
package test
import (
"fmt"
"testing"
)
type Pet struct {
}
func (p *Pet) Speak() {
fmt.Println("...")
}
func (p *Pet) SpeakTo(host string) {
p.Speak()
fmt.Println(host)
}
type Dog struct {
Pet
}
//方法重写
func (d *Dog) Speak() {
fmt.Println("Wang")
}
func TestInhenrit(t *testing.T) {
//var d *Pet = new(Dog),这一句会报错
//var d *Pet = *Pet(new(Dog)),强制转换也会报错
//通过打印结果以及这两句话的错误可以断定内嵌方法也无法完成继承
d := new(Dog)
d.SpeakTo("Jack")
}
从以上的手段我们可以看到Go语言是不支持继承的。
五.多态
package test
import (
"fmt"
"testing"
)
type Programmer interface {
WriteHelloWorld() string
}
type GoProgrammer struct {
}
type JavaProgrammer struct {
}
func (g *GoProgrammer) WriteHelloWorld() string {
return "I am Go"
}
func (j *JavaProgrammer) WriteHelloWorld() string {
return "I am Java"
}
//接口接收指针类型
func testMulti(p Programmer) {
fmt.Printf("%T %s\n", p, p.WriteHelloWorld())
}
func TestClient(t *testing.T) {
g := new(GoProgrammer)
j := new(JavaProgrammer)
testMulti(g)
testMulti(j)
}
六.空接口与断言
package test
import (
"fmt"
"testing"
)
//p是空接口,它类似与c/c++中的void*,可以接受任意类型
func testType(p interface{}) {
//我们可以使用断言判断传入的空接口的类型
//v代表结果返回值,ok是bool类型,根据ok判断是否转换成功
if v, ok := p.(int); ok {
fmt.Printf("%d: Integer Type\n", v)
return
}
if v, ok := p.(string); ok {
fmt.Printf("%s: tring Type\n", v)
return
}
fmt.Println("Unkown Type")
}
func TestClient(t *testing.T) {
testType(1)
testType("a")
}
当然上面的写法比较重复,下面写法逻辑结构更加清晰与明确
package test
import (
"fmt"
"testing"
)
func testType(p interface{}) {
switch v := p.(type) {
case int:
fmt.Println("int", v)
case string:
fmt.Println("string", v)
default:
fmt.Println("Unkown")
}
fmt.Println("Unkown Type")
}
func TestClient(t *testing.T) {
testType(1)
testType("a")
}
最后我们总结一下Go接口编写实践的一些建议