【Go语言】面向对象扩展——接口

来源:互联网 时间:1970-01-01

简单地说 Interface是一组Method的组合,可以通过Interface来定义对象的一组行为。
如果某个对象实现了某个接口的所有方法,就表示它实现了该借口,无需显式地在该类型上添加接口说明。

Interface是一个方法的集合,它里面没有其他类型变量,而且Method只用定义原型 不用实现

①接口定义

1.命名时习惯以"er"结尾,如Printer Reader Writer

2.一个Interface的Method不宜过多,一般0~3个

3.一个Interface可以被任意的对象事项;相应地,一个对象也可以实现多个Interface

示例:

type People struct{

Name string

}

type Student struct{

People

School string

}

type Teacher struct{

People

Department string

}

func (p People) SayHi(){}

func (s Student) SayHi(){}

func (t Teacher) SayHi(){}

func (s Student) Study(){}

//根据struct的方法提取接口 从而使struct自动实现了该接口

type Speaker interface{

SayHi()

}

type Learner interface{

SayHi()

Study()

}

 

上面的例子中,Speaker接口被对象People,Teacher,Student实现;而Student同时实现了接口Speaker和Learner。

接口组合:

type SpeakLearner interface {

Speaker

Learner

}//组合后使得SpeakLearner具有Speaker和Learner的功能

 

空接口:

任何类型都实现了空接口,相当于Java中的Object类

func test(a interface{}){}//该方法可以接受任意类型(int rune float32 struct...)的参数

 

②接口执行机制和接口赋值

首先介绍一种Go语言带接收者(Receiver)的函数机制(下面的两种情况执行结果一样,涉及到struct成员值改变时仍然一样)

情况1:

package main

import (

"fmt"

)

type People struct {

Name string

}

func (p People) SayHi(){ //此处的Receiver是strcut

fmt.Println("hello, this is", p.Name)

}

func (p *People) Study(){//此处的Receiver是****struct

fmt.Printf("%s is studying\n", p.Name)

}

type SpeakLearner interface {

SayHi()

Study()

}

func main() {

people := People{"zhangsan"}//这里的people为People类型

people.SayHi()

people.Study()

}

 

情况2:

func main() {

people := &People{"zhangsan"}//这里的people为**People类型,即指针

people.SayHi()

people.Study()

}

 

通过上面的例子可以看出Receiver为People和*People的函数均可被People或者*People两种类型调用,接下来借可能有在调用过程中People与*People之间的转换问题

看下面的例子:

package main

import (

"fmt"

)

type Example struct{

Integer1 int

Integer2 int

}

func (e Example) Assign(num1 int, num2 int) {

e.Integer1, e.Integer2 = num1, num2

}

func (e *Example) Add(num1 int, num2 int) {

e.Integer1 +=num1

e.Integer2 +=num2

}

func main(){

var e1 Example = Example{3,4}

e1.Assign(1,1)

fmt.Println(e1)

e1.Add(1,1)

fmt.Println(e1)

var e2 *Example = &Example{3,4}

e2.Assign(1,1)

fmt.Println(e2)

e2.Add(1,1)

fmt.Println(e2)

}

 

以上程序的执行结果为:

{3,4}

{4,5}

&{3,4}

&{4,5}

 

可以看出实际执行的过程按函数定义前的Receiver类型执行。

对于接口的执行机制:

1.T仅拥有属于T类型的方法集,而*T则同时拥有(T+*T)方法集
2.基于T实现方法,表示同时实现了interface和interface(*T)接口
3.基于*T实现方法,那就只能是对interface(*T)实现接口

type Integer int

func (a Integer) Less(b Integer) bool {

return a < b

}

func (a *Integer) Add(b Integer) {

*a += b

}

相应地,我们定义接口LessAdder,如下:

type LessAdder interface {

Less(b Integer) bool

Add(b Integer)

}

现在有个问题:假设我们定义一个Integer类型的对象实例,怎么将其赋值给LessAdder接口呢?

应该用下面的语句(1),还是语句(2)呢?

var a Integer = 1

var b LessAdder = &a ... (1)

var b LessAdder = a ... (2)

答案是应该用语句(1)。原因在于,Go语言可以根据下面的函数:

func (a Integer) Less(b Integer) bool

即自动生成一个新的Less()方法:

func (a *Integer) Less(b Integer) bool {

return (*a).Less(b)

}

这样,类型*Integer就既存在Less()方法,也存在Add()方法,满足LessAdder接口。

而从另一方面来说,根据

func (a *Integer) Add(b Integer)

这个函数无法自动生成以下这个成员方法:

func (a Integer) Add(b Integer) {

(&a).Add(b)

}

因为(&a).Add()改变的只是函数参数a,对外部实际要操作的对象并无影响,这不符合用

户的预期。所以,Go语言不会自动为其生成该函数。

因此,类型Integer只存在Less()方法,缺少Add()方法,不满足LessAdder接口,故此上面的语句(2)不能赋值。

 

接口赋值举例:

package main

import(

"fmt"

)

//定义对象People、Teacher和Student

type People struct {

Name string

}

type Teacher struct{

People

Department string

}

type Student struct{

People

School string

}

//对象方法实现

func (p People) SayHi() {

fmt.Printf("Hi, I'm %s. Nice to meet you!\n",p.Name)

}

func (t Teacher) SayHi(){

fmt.Printf("Hi, my name is %s. I'm working in %s .\n", t.Name, t.Department)

}

func (s Student) SayHi() {

fmt.Printf("Hi, my name is %s. I'm studying in %s.\n", s.Name, s.School)

}

func (s Student) Study() {

fmt.Printf("I'm learning Golang in %s.\n", s.School)

}

//定义接口Speaker和Learner

type Speaker interface{

SayHi()

}

type Learner interface{

SayHi()

Study()

}

func main() {

people := People{"张三"}

teacher := Teacher{People{"郑智"}, "Computer Science"}

student := Student{People{"李明"}, "Yale University"}

var is Speaker //定义Speaker接口类型的变量

is = people //is能存储People

is.SayHi()

is = teacher //is能存储Teacher

is.SayHi()

is = student

is.SayHi() //is能存储Student

var il Learner

il = student //Learner类型接口的变量能存储Student

il.Study()

}

 

执行结果为:

Hi, I'm 张三. Nice to meet you!

Hi, my name is 郑智. I'm working in Computer Science .

Hi, my name is 李明. I'm studying in Yale University.

I'm learning Golang in Yale University.

 

通过这个例子可以 看到(如同Java等语言)接口机制在多态和创建可扩展可重用的代码时的重要作用

③匿名字段和接口转换

若果接口类型S内部嵌入了接口类型T(匿名),则接口匿名字段方法集规则如下:

1.如果S嵌入匿名类型T,则S方法集包含T方法集。
2.如果S嵌入匿名类型*T,则S方法集包含*T方法集(包括Riceiver为T和*T的方法)。
3.如果S嵌入匿名类型T或*T,则*S方法集包含*T方法集(包括Riceiver为T和*T的方法)。(重要)

例如:

package main

import(

"fmt"

)

type People struct {

Name string

}

type S1 struct{

People //S1类型嵌入匿名People

Department string

}

type S2 struct{

*People //S2类型嵌入匿名*People

Department string

}

func (p People) Say1() {

fmt.Printf("Hi, I'm %s. Say1111\n",p.Name)

}

func (p *People) Say2() {

fmt.Printf("Hi, I'm %s. Say2222\n",p.Name)

}

type Speaker interface{

Say1()

Say2()

}

func main() {

people := People{"张三"}

s1 := S1{People{"郑智"}, "Computer Science"}

s2 := S2{&People{"李明"}, "Math"}

var is Speaker

is = &people //*People实现了Speaker接口

is.Say1()

is.Say2()

//is = s1 //S1类型嵌入匿名People 不存在Say2()方法 因而未实现Speaker接口

//错误提示: cannot use s1 (type S1) as type Speaker in assignment:

//S1 does not implement Speaker (Say2 method has pointer receiver)

is = s2 //S2类型嵌入匿名*People 因而(p People) Say1()和(p *People) Say2()方法都有 实现了Speaker接口

is.Say1()

is.Say2()

is = &s1 //S1类型嵌入匿名People *S1 实现了Speaker接口

is.Say1()

is.Say2()

is = &s2 //S2类型嵌入匿名*People *S2 实现了Speaker接口

is.Say1()

is.Say2()

}

 

 执行结果为:

Hi, I'm 张三. Say1111

Hi, I'm 张三. Say2222

Hi, I'm 李明. Say1111

Hi, I'm 李明. Say2222

Hi, I'm 郑智. Say1111

Hi, I'm 郑智. Say2222

Hi, I'm 李明. Say1111

Hi, I'm 李明. Say2222

 

从而证明了匿名字段方法集的3条规则。

接口转换类似于说是接口继承规则 可认为是实现复杂接口(方法多)向简单接口(方法少)转换,其中简单接口中的方法在复杂接口中均有声明 。例如:

package main

import(

"fmt"

)

type People struct {

Name string

}

type Student struct{

People

School string

}

func (p People) GetPeopleInfo() {

fmt.Println(p)

}

func (s Student) GetStudentInfo() {

fmt.Println(s)

}

type PeopleInfo interface{

GetPeopleInfo()

}

type StudentInfo interface{

GetPeopleInfo()

GetStudentInfo()

}

func main() {

var is StudentInfo = Student{People{"李明"}, "Yele University"}

is.GetStudentInfo()

is.GetPeopleInfo()

var ip PeopleInfo = is

ip.GetPeopleInfo()

///ip.GetStudentInfo() note:ip.GetStudentInfo undefined

}

 

④接口类型推断:Comma-ok断言和Switch测试

 利用接口类型推断可以 反向知道接口类型变量里面实际保存的是哪一种类型的对象。

Go语言中,常用两种方法可以进行接口类型推断,即Comma-ok断言和Switch测试

Comma-ok断言使用格式如下

value,ok = element.(T)

 

用法示例:

//利用Comma-ok断言进行接口类型推断

package main

import(

"fmt"

)

type People struct{

Name string

Age int

}

//定义空接口用于存储任意类型数据类型

type Object interface{}

func main() {

people := People{"张三", 20}

objs := make([]Object, 4)

objs[0], objs[1], objs[2], objs[3] = 1, true, "Hello", people

for index, element := range objs{

if value, ok := element.(int); ok{

fmt.Printf("objs[%d]类型是int,value=%d\n", index, value)

}else if value, ok := element.(bool); ok{

fmt.Printf("objs[%d]类型是bool,value=%v\n", index, value)

}else if value, ok := element.(string); ok{

fmt.Printf("objs[%d]类型是string,value=%s\n", index, value)

}else if value, ok := element.(People); ok{

fmt.Printf("objs[%d]类型是Peole,value=%v\n", index, value)

}else{

fmt.Printf("objs[%d]类型未知\n", index)

}

}

}

 

结果是这样的:

objs[0]类型是int,value=1

objs[1]类型是bool,value=true

objs[2]类型是string,value=Hello

objs[3]类型是Peole,value={张三 20}

 

使用Switch测试判断接口类型,程序结构更加简洁,示例如下(只修改了示例中的main函数):

func main() {

people := People{"张三", 20}

objs := make([]Object, 4)

objs[0], objs[1], objs[2], objs[3] = 1, true, "Hello", people

for index, element := range objs{

switch value := element.(type){

case int:

fmt.Printf("objs[%d]类型是int,value=%d\n", index, value)

case bool:

fmt.Printf("objs[%d]类型是bool,value=%v\n", index, value)

case string:

fmt.Printf("objs[%d]类型是string,value=%s\n", index, value)

case People:

fmt.Printf("objs[%d]类型是Peole,value=%v\n", index, value)

default:

fmt.Printf("objs[%d]类型未知\n", index)

}

}

}

 

执行结果Comma-ok方法相同,但是程序简洁了许多。


相关阅读:
Top