9. interface

what is interface

Simply put, interface is a combination of methods. We use interface to define a set of behaviors of an object.

In our last example in the previous chapter, both Student and Employee can say hi. Although their internal implementations are different, that is not important. What is important is that they can both say hi.

Let's continue with more extensions, Student and Employee implement another method Sing, then Student implements method BorrowMoney and Employee implements SpendSalary.

In this way, Student implements three methods: Sayhi, Sing, and BorrowMoney; while Employee implements Sayhi, Sing, and SpendSalary.

The combination of the above methods is called interface (implemented by the objects Student and Employee). For example, both Student and Employee implement the interface: Sayhi and Sing, which means these two objects are of this interface type. Employee does not implement this interface: Sayhi, Sing and BorrowMoney, because Employee does not implement the BorrowMoney method.

interface type

The interface type defines a set of methods. If an object implements all methods of an interface, then the object implements the interface. For detailed syntax, refer to the example below

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) {
    
    
	s.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)
}

From the above code we can know that interface can be implemented by any object. We see that the above Men interface is implemented by Human, Student and Employee. In the same way, an object can implement any number of interfaces. For example, the above Student implements two interfaces: Men and YonggChap.

Finally, any type implements an empty interface (we define it like this: interface{}), which is an interface containing 0 methods.

interface value

So what values ​​can be stored in the interface? If we define an interface variable, then this variable can store any type of object that implements this interface. For example, in the above example, we define a variable m of Men interface type, then the value of Human, Student or Employee can be stored in m.

Because m can hold these three types of objects, we can define a slice containing Men type elements. This slice can be assigned to an object of any structure that implements the Men interface. This is different from our traditional slice. .

Let's take a look at the following example

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()
	}
}

Through the above code, you will find that interface is a set of abstract methods, which must be implemented by other non-interface types and cannot be self-implemented. go implements duck-typing through interface: that is, "when you see a bird walking Like a duck, swims like a duck, and quacks like a duck, then this bird can be called a duck."

empty interface

The empty interface (interface{}) does not contain any methods. Because of this, all types implement the empty interface. The empty interface does not play any role in the description (because it does not contain any method), but the empty interface is very useful when we need to store any type of value, because it can store any type of value. It is somewhat similar to the void* type in C language.

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

If a function takes interface{} as a parameter, it can accept any type of value as a parameter. If a function returns interface{}, it can also return any type of value.

interface function parameters

Interface variables can hold any object that implements the interface type. This provides us with some additional thinking when writing functions (including methods). Can we define interface parameters to let the function accept various types of parameters?

For example: fmt.Println is a commonly used function, but have you noticed that it can accept any type of data. Open the source code file of fmt, you will see such a definition:

type Stringer interface {
    
     
    String() string 
}

In other words, any type that implements the String method can be called as a parameter by fmt.Println. Let’s give it a try.

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 - phone:" + h.phone + " "
}
func main() {
    
    
	Bob := Human{
    
    "Bob", 39, "000-7777-XXX"}
	fmt.Println("This Human is : ", Bob)
}

Note: For objects that implement the error interface (that is, objects that implement Error() string), when using fmt to output, the Error() method will be called, so there is no need to define the String() method.

The type of interface variable storage

We know that any type of value can be stored in the variable of interface (this type implements interface). So how do we reversely know which type of object is actually stored in this variable? There are two methods commonly used at present:

1. Comma-ok assertion

There is a grammar in Go language that can directly determine whether it is a variable of this type: value, ok = element.(T), where value is the value of the variable, ok is a bool type, element is an interface variable, and T is the type of assertion. .

If the element does store a value of type T, then ok returns true, otherwise it returns false.

Let us understand more deeply through an example.

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)
		}
	}
}

Isn’t it very simple? At the same time, have you noticed that there are multiple ifs? Remember what I said in the introduction process earlier that variables are allowed to be initialized in ifs.

Maybe you noticed that the more types we assert, the more ifels we have, which is why the switch introduced below is introduced.

2. switch test

The best explanation is a code example. Now let us rewrite the above implementation.

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)
		}
	}
}

One thing that needs to be emphasized here is: element.(type) syntax cannot be used in any logic outside of switch . If you want to determine a type outside of switch, use comma-ok.

Embed interface

What's really attractive about Go is its built-in logical syntax, just like the anonymous fields we learned when we were learning Struct. How elegant it is. If the same logic is introduced into the interface, wouldn't it be even more perfect? If an interface1 is used as an embedded field of interface2, then interface2 implicitly includes the method in interface1.

We can see that there is such a definition in the source code package 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 
}

We see that sort.Interface is actually an embedded field, and all methods of sort.Interface are implicitly included. That is, the following three methods

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) 
}

Another example is io.ReadWriter under the io package, which contains the two interfaces Reader and Writer under the io package.

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

reflection

Go language implements reflection, which is the state of dynamic runtime. The package we generally use is the reflect package.

Using reflect is generally divided into three steps. Here is a brief explanation: To reflect a value of a type (these values ​​all implement the empty interface), you first need to convert it into a reflect object (reflect.Type or reflect.Value, depending on the type) different functions are called). The two acquisition methods are as follows:

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

After converting it into a reflect object, we can perform some operations, that is, convert the reflect object into the corresponding value, such as

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

Obtaining the reflection value can return the corresponding type and value

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()) 

Finally, for reflection, the reflected fields must be modifiable. We have learned about passing by value and passing by reference before. The same is true here. The reflected fields must be readable and writable. This means that if it is written like this, then an error will occur

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

If you want to modify the corresponding value, you must write like this

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

The above is just a brief introduction to reflection. A deeper understanding requires continuous practice in programming.

Guess you like

Origin blog.csdn.net/u012534326/article/details/120399794