Files
build-web-application-with-…/2.6.md
2012-09-02 22:59:27 +08:00

14 KiB
Raw History

#2.6interface

##interface Go语言里面设计最精妙的应该算interface它让面向对象内容组织实现非常的方便当你看完这一章你就会被interface的设计巧妙所折服。 ###什么是interface 简单的说interface是一组method的组合我们通过interface来定义一个对象的一组行为。

我们前面一章最后一个例子中Student和Employee都能Sayhi虽然他们的内部实现不一样但是那不重要重要的是他们都能say hi

让我们来继续做更多的扩展Student和Employee实现另一个方法Sing然后Student实现方法BorrowMoney而Employee实现SpendSalary。

这样Student实现了三个方法Sayhi、Sing、BorrowMoney而Employee实现了Sayhi、Sing、SpendSalary。

这些Student和Employee实现的一组方法就是interface。例如Student和Employee都满足了这个interfaceSayhi和Sing。而Employee就没有满足这个interfaceSayhi、Sing和BorrowMoney因为Employee没有实现BorrowMoney这个方法。 ###interface类型 interface类型是定义一组方法这组方法被一些对象实现。详细的语法参考下面这个例子

type Human struct {
	name string
	age int
	phone string
}

type Student struct {
    Human //匿名字段Human
    school string
    loan float32
}

type Employee struct {
    Human //匿名字段Human
    company string
    money float32
}

//Human对象实现Sayhi方法
func (h *Human) SayHi() {
    fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}

// Human对象实现Sing方法
func (h *Human) Sing(lyrics string) {
    fmt.Println("La la, la la la, la la la la la...", lyrics)
}

//Human对象实现Guzzle放噶
func (h *Human) Guzzle(beerStein string) {
    fmt.Println("Guzzle Guzzle Guzzle...", beerStein)
}

// Employee重载Human的Sayhi方法
func (e *Employee) SayHi() {
    fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,
        e.company, e.phone) //Yes you can split into 2 lines here.
}

//Student实现BorrowMoney方法
func (s *Student) BorrowMoney(amount float32) {
    loan += amount // (again and again and...)
}

//Employee实现SpendSalary方法
func (e *Employee) SpendSalary(amount float32) {
    e.money -= amount // More vodka please!!! Get me through the day!
}

// 定义interface
type Men interface {
    SayHi()
    Sing(lyrics string)
    Guzzle(beerStein string)
}

type YoungChap interface {
    SayHi()
    Sing(song string)
    BorrowMoney(amount float32)
}

type ElderlyGent interface {
    SayHi()
    Sing(song string)
    SpendSalary(amount float32)
}

通过上面的代码我们可以知道interface可以被任意的对象实现。我们看到上面的Men interface被Human、Student和Employee实现。同理一个对象可以实现任意多个interface例如上面的Student实现了Men和YonggChap两个interface。

最后任意的类型都实现了空interface(我们这样定义interface{})也就是包含0个method的interface。

###interface值 那么interface里面到底能存什么值呢如果我们定义了一个interface的变量那么这个变量里面可以存实现这个interface的任意类型。例如上面例子中我们定义了一个Men interface类型的变量m那么m里面可以存Human、Student或者Employee值。

那么既然m能够存这三种类型的数值那么我们可以定义一个包含men元素的slice这样这个slice里面可以包含各种组合了这个和我们传统意义上面的slice有所不同。

让我们来看一下下面这个例子

package main
import "fmt"

type Human struct {
    name string
    age int
    phone string
}

type Student struct {
    Human //匿名字段
    school string
    loan float32
}

type Employee struct {
    Human //匿名字段
    company string
    money float32
}

//Human实现Sayhi方法
func (h Human) SayHi() {
    fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}

//Human实现Sing方法
func (h Human) Sing(lyrics string) {
    fmt.Println("La la la la...", lyrics)
}

//Employee重载Human的SayHi方法
func (e Employee) SayHi() {
    fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,
        e.company, e.phone) //Yes you can split into 2 lines here.
	}

// Interface Men被Human,Student和Employee实现
// 因为这三个类型都实现了这两个方法
type Men interface {
    SayHi()
    Sing(lyrics string)
}

func main() {
    mike := Student{Human{"Mike", 25, "222-222-XXX"}, "MIT", 0.00}
    paul := Student{Human{"Paul", 26, "111-222-XXX"}, "Harvard", 100}
    sam := Employee{Human{"Sam", 36, "444-222-XXX"}, "Golang Inc.", 1000}
    Tom := Employee{Human{"Sam", 36, "444-222-XXX"}, "Things Ltd.", 5000}

    //定义Men类型的变量i
    var i Men

    //i能存储Student
    i = mike
    fmt.Println("This is Mike, a Student:")
    i.SayHi()
    i.Sing("November rain")

    //i也能存储Employee
    i = Tom
    fmt.Println("This is Tom, an Employee:")
    i.SayHi()
    i.Sing("Born to be wild")

    //定义了slice Men
    fmt.Println("Let's use a slice of Men and see what happens")
    x := make([]Men, 3)
    //T这三个都是不同类型的元素但是他们实现了interface同一个接口
    x[0], x[1], x[2] = paul, sam, mike

    for _, value := range x{
        value.SayHi()
    }
}

通过上面的代码你会发现interface其实他们是一组抽象的方法他不自己实现自己。他们就像如果谁能实现我定义的方法那么谁就拥有我。

###空interface 空interface(interface{})不包含任何的method正因为如此所有的类型都实现了空interface。空interface对于描述他包含的method没有任何的用处但是对于我们存储任意类型的数值的时候相当有用因为他可以存储任意类型的数值。

// 定义a为空接口
var a interface{}
var i int = 5
s := "Hello world"
// a可以存储任意类型的数值
a = i
a = s

一个函数把interface{}作为参数那么他可以接受任意类型的数值作为参数如果一个函数返回interface{},那么也就可以返回任意类型的数值。是不是很有用啊! ###interface函数参数 通过上面interface变量可以存储任意实现该interface值的类型数据给我们编写函数(包括method)提供了一些额外的思考我们是不是可以通过定义interface参数让函数接受各种类型的参数。

举个例子我们已经知道fmt.Println是我们常用的一个函数但是你是否注意到它可以接受任意类型的数据。其实我们打开fmt的源码包他里面定义了一个interface Stringer

type Stringer interface {
	 String() string
}

任何实现了String方法的类型都能调用fmt.Println,让我们来试一试

package main
import (
    "fmt"
    "strconv"
)

type Human struct {
	name string
	age int
	phone string
}

//通过这个方法 Human 实现了 fmt.Stringer
func (h Human) String() string {
    return "❰"+h.name+" - "+strconv.Itoa(h.age)+" years -  ✆ " +h.phone+"❱"
}

func main() {
    Bob := Human{"Bob", 39, "000-7777-XXX"}
    fmt.Println("This Human is : ", Bob)
}

现在我们再回顾一下我们前面写的写的Box里面定义的Color的method string。其实我们也是实现了fmt.Stringer这个interface

//实现同样的功能
fmt.Println("The biggest one is", boxes.BiggestsColor().String())
fmt.Println("The biggest one is", boxes.BiggestsColor())

###interface变量存储的类型

我们知道interface的变量里面可以存储任意类型的数值(该类型实现了interface)。那么我们怎么反向知道这个变量里面到底存储的是什么类型呢?目前常用的有两种方法:

  • Comma-ok断言

    Go语言里面有一个语法可以直接判断是否是该类型的变量 value, ok = element.(T)这里value就是变量的值ok是一个bool类型element是interface变量T是断言的类型。

    如果element里面确实存储了T类型的数值那么ok返回true否则返回false。

    让我们通过一个例子来更加深入的理解。

      package main
    
      import (
      	"fmt"
      	"strconv"
      )
    
      type Element interface{}
      type List [] Element
    
      type Person struct {
      	name string
      	age int
      }
    
      //定义了String方法实现了fmt.Stringer
      func (p Person) String() string {
      	return "(name: " + p.name + " - age: "+strconv.Itoa(p.age)+ " years)"
      }
    
      func main() {
      	list := make(List, 3)
      	list[0] = 1 // an int
      	list[1] = "Hello" // a string
      	list[2] = Person{"Dennis", 70}
    
      	for index, element := range list {
      		if value, ok := element.(int); ok {
          		fmt.Printf("list[%d] is an int and its value is %d\n", index, value)
      		} else if value, ok := element.(string); ok {
          		fmt.Printf("list[%d] is a string and its value is %s\n", index, value)
      		} else if value, ok := element.(Person); ok {
          		fmt.Printf("list[%d] is a Person and its value is %s\n", index, value)
      		} else {
          		fmt.Println("list[%d] is of a different type", index)
      		}
      	}
      }
    

    是不是很简单啊同时你是否注意到了多个ifs里面还记得我前面介绍流程里面讲过if里面允许初始化变量。

    也许你注意到了我们断言的类型越多那么ifelse也就越多所以才引出了下面要介绍的switch。

  • switch测试

    最好的讲解就是代码例子,现在让我们重写上面的这个实现

      package main
    
      import (
      	"fmt"
      	"strconv"
      )
    
      type Element interface{}
      type List [] Element
    
      type Person struct {
          name string
      	age int
      }
    
      //打印
      func (p Person) String() string {
      	return "(name: " + p.name + " - age: "+strconv.Itoa(p.age)+ " years)"
      }
    
      func main() {
      	list := make(List, 3)
      	list[0] = 1 //an int
      	list[1] = "Hello" //a string
      	list[2] = Person{"Dennis", 70}
    
      	for index, element := range list{
      		switch value := element.(type) {
          		case int:
              		fmt.Printf("list[%d] is an int and its value is %d\n", index, value)
          		case string:
              		fmt.Printf("list[%d] is a string and its value is %s\n", index, value)
          		case Person:
              		fmt.Printf("list[%d] is a Person and its value is %s\n", index, value)
          		default:
              		fmt.Println("list[%d] is of a different type", index)
      		}
      	}
      }
    

    这里有一点需要强调的是:element.(type)语法不能在switch外的任何逻辑里面使用如果你要在switch外面判断一个类型就使用comma-ok

###匿名interface Go里面真正吸引人的是他内置的逻辑语法就像我们在学习Struct时学习的匿名字段多么的优雅啊那么相同的逻辑引入到interface里面那不是更加完美了。如果一个interface1作为interface2的一个匿名字段那么interface2隐式的包含了interface1里面的method。

我们可以看到源码包container/heap里面有这样的一个定义

type Interface interface {
	sort.Interface //匿名字段sort.Interface
	Push(x interface{}) //a Push method to push elements into the heap
	Pop() interface{} //a Pop elements that pops elements from the heap
}

我们看到sort.Interface其实就是匿名字段把sort.Interface的所有method给隐式的包含进来了。也就是下面三个方法

type Interface interface {
	// Len is the number of elements in the collection.
	Len() int
	// Less returns whether the element with index i should sort
	// before the element with index j.
	Less(i, j int) bool
	// Swap swaps the elements with indexes i and j.
	Swap(i, j int)
}

另一个例子就是io包下面的 io.ReadWriter 他包含了io包下面的Reader和Writer两个interface。

// io.ReadWriter
type ReadWriter interface {
	Reader
	Writer
}	

###反射 Go语言实现了反射所谓反射就是动态运行时的状态。我们一般用到的包是reflect包。如何运用reflect包官方的这篇文章详细的讲解了reflect包的实现原理laws of reflection

下面我简要的讲解一下一般的使用reflect主要的实现过程分成三部首先我们要去反射的值都是一个类型的值首先我们需要把它转化成interface(reflect.Type或者reflect.Value根据不同的情况调用不同的函数)。这两种获取方式如下:

t := reflect.TypeOf(i)    //得到类型的元数据,通过t我们能获取类型定义里面的所有元素
v := reflect.ValueOf(i)   //得到实际的值通过v我们获取存储在里面的值还可以去改变值

获取这个对象之后我们就可以进行一些操作了,例如

tag := t.Elem().Field(0).Tag  //获取定义在strcut里面的标签
name := v.Elem().Field(0).String()  //获取存储在第一个字段里面的值

获取反射值能返回相应的类型和数值

var x float64 = 3.4
v := reflect.ValueOf(x)
fmt.Println("type:", v.Type())
fmt.Println("kind is float64:", v.Kind() == reflect.Float64)
fmt.Println("value:", v.Float())	

最后,反射的话,那么反射的字段必须是可修改的,我们前面学习过传值和传引用,这个里面也是一样的道理,反射的字段必须是可读写的意思是,如果下面这样写,那么会发生错误

var x float64 = 3.4
v := reflect.ValueOf(x)
v.SetFloat(7.1)

如果要修改相应的值,必须这样写

var x float64 = 3.4
p := reflect.ValueOf(&x)
v := p.Elem()
v.SetFloat(7.1)

使用反射需要自己在编程中不断的深入去了解,我这边只能大概的介绍一些。

LastModified

  • Id