Learn Go: 15. Interface

>>Original address

Learn what

  1. What is an interface?

  2. How to define interfaces?

  3. How to use the interface?

  4. How to embed interfaces?

  5. How to assign values between interfaces?

  6. How to infer the actual type of interface?

  7. How to use an empty interface?

concept

The interface defines the rules of the implementer by defining abstract methods. The concept is a little similar to that in other languages, but for the implementation of the interface in Go language, the coupling between the interface and the interface is lower and the flexibility is higher.

In order to understand the concept of interface in a popular way, let me give you an example, "if you are entrusted by Nuwa to create people, but you have made requirements for creating people, you should have the action of eating and drinking, but it doesn't matter how everyone eats and drinks. As long as you have these two actions, you will succeed in creating people".

In this example, the requirements formulated by Nuwa are interfaces. If you do it according to the requirements of human creation, it is called the implementation of interfaces.

I don't know if it's clear. Bless you.

definition

Now define an interface for People and define two actions for eating and drinking.

type People interface {

  // Optional parameter name: Eat(string) error

    Eat(thing string) error

    Drink(thing string) error

}
  • The Eat and sink methods do not need to be implemented.

  • func keyword is not required before method.

  • The parameter name and return name of the method may not be written.

After definition, you can directly declare a variable of this interface type.

var p People

The p variable is not initialized, and the value is nil.

Implementation interface

The work of interface implementation is entrusted to the user-defined type. When the user-defined type implements all the methods of the interface, it implements the interface.

type LaoMiao struct {

    Name string

    Age  int

}

func (l LaoMiao) Eat(thing string) error {

    fmt.Println("Steal in the company" + thing)

    return nil

}

func (l LaoMiao) Drink(thing string) error {

    fmt.Println("Steal drinks in the company" + thing)

    return nil

}
  • LaoMiao implements all methods in the People interface, which shows that it implements the interface.

  • There is no need to use the implementation keyword in other languages to implement.

  • Multiple interfaces can be implemented at the same time.

If you look at another picture, it may be clearer, as follows:

The "implementer" in the figure includes the methods of interfaces A and B, and it implements both interfaces.

Use of interfaces

After the interface is implemented, the instantiation of this type can be assigned to the interface type.

var p People = LaoMiao{}

p.Eat("Peach")

// output

Stealing peaches in the company

p is the interface type, and the actual implementation is LaoMiao type.

You may wonder why I don't call it directly, like the following:

m := LaoMiao{}

m.Eat("Peach")

The above code does not use the People interface type, but if I define another type to implement the People interface, the benefits will be reflected.

type LaoSun struct {

    Name string

    Age  int

}

func (l LaoSun) Eat(thing string) error {

    fmt.Println("Eat in the car" + thing)

    return nil

}

func (l LaoSun) Drink(thing string) error {

    fmt.Println("Drink in the car" + thing)

    return nil

}

Another type is added to implement. See that this is LaoSun and the type above is LaoMiao.

Now let's start thinking about a question. If I want to call these two types of methods, and the calling code is only written once, what should I do? Take a breath, I tell you, nature uses the interface.

// interface/main.go

// ...

func Run(p People) {

    thing1, thing2 := "Peach", "cola"

    p.Eat(thing1)

    p.Drink(thing2)

}

func main() {

    Run(LaoMiao{})

    Run(LaoSun{})

}

// output

Stealing peaches in the company

Steal coke in the company

Eat peaches in the car

Drink coke in the car

A Run function is added to the code. The type accepted by the function is the interface type. The main function passes the two types that implement the interface to the function.

Receiver type and interface

In the above code, all type receivers that implement interfaces are value types, for example:

func (l LaoSun) Eat(thing string) error {

    // ...

}

The receiver l type is LaoSun. If it is a pointer, the receiver should be * LaoSun.

When calling with interface type, the type accepted by the interface is value type, for example:

Run(LaoSun{})

The parameter LaoSun {} of this function is of value type, but it can also pass the pointer type run (& LaoSun {}), and the compiler will dereference it.

If the receiver is a pointer type, the pointer type must be used when passing values to the interface, for example:

type GouDan struct {

   Name string

   Age  int

}

func (l *GouDan) Eat(thing string) error {

    fmt.Println("You take care of me" + thing)

    return nil

}

func (l *GouDan) Drink(thing string) error {

    fmt.Println("You don't care what I drink" + thing)

    return nil

}

Redefine a type to implement the People interface, and the receiver of the method is the pointer type. If the value type is passed to the interface, the compiler will report an error.

Run(GouDan{})

// output

cannot use GouDan{} (type GouDan) as type People in argument to Run:

    GouDan does not implement People (Drink method has pointer receiver)

Correctly, only pointer types can be passed.

Run(&GouDan{})

or

var sun *LaoSun = &LaoSun{}

Run(sun)

Interface embedding

An interface may contain another interface, for example:

type Student interface {

   People

   Study()

}
  • A Student interface is defined. Naturally, there will be eating and drinking actions for the Student interface. Therefore, there is no need to repeat the definition, just embed the People interface.

  • If a user-defined type wants to implement the Student interface, it needs to implement both the methods defined in the embedded interface and the methods defined by itself.

  • Multiple interfaces can be embedded.

Interface and interface assignment

In the above code, the People interface is embedded into the Student interface. At this time, the Student interface type variable can be assigned to the People type variable.

Example:

var stu Student

var pl People = stu

If the People type is not embedded, but only the Student interface contains its methods, the above interface and interface assignment are also allowed.

type Student interface {

    Eat(thing string) error

    Drink(thing string) error

    Study()

}

Summary: the large interface contains the methods of the small interface, and the large interface can be assigned to the small interface.

Empty interface

An empty interface means that no abstract method is defined, as follows:

type Empty interface {}

The Empty type is now an Empty interface that can accept any type.

var str Empty = "character string"

var num Empty = 222

When used in normal projects, in order to make it easier, there is no need to define an empty interface, and interface {} is directly used as the type.

var str interface{} = "character string"

var num interface{} = 222

Summary: all types implement empty interfaces, that is, empty interfaces can accept variables of any type.

Type inference

In an interface variable, if you want to know who the specific implementation type of the interface variable is, you need to use type inference.

1. Interface to implementer

v := var1.(T)
  • T indicates the type you need to infer.

  • v is a variable of type T after conversion.

  • var1 can be an empty interface.

Example:

var people People

// Convert People type to LaoMiao value type

people = LaoMiao{}

val := people.(LaoMiao)

// Convert People type to LaoMiao pointer type

people = &LaoMiao{}

peo := people.(*LaoMiao)

It can be seen from the example that the type stored in the interface variable must be the same as the type inferred. If not, the compiler will report an error.

people = LaoMiao{}

// report errors

val := people.(*LaoMiao)

// correct

val := people.(LaoMiao)

2. Is it inferable

If the actual type stored in the interface variable is uncertain, it must be judged. If it cannot be inferred, the compiler will report an error.

v, ok := var1.(T)

Judging is actually adding an additional return value when inferring the type. If the inferred ok value is true, otherwise it is false.

people = LaoMiao{}

val1, ok := people.(*LaoMiao)

fmt.Println(ok)

val2, ok := people.(LaoMiao)

fmt.Println(ok)

// output

false

true

3. type-switch

This knowledge point was actually long ago Process control I've talked about it in an article. I'll say it again and add.

var data interface{}

data = "111"

// data is the interface type, and. (type) gets the actual type

// Assign the value of the actual type to the d variable

switch d := data.(type) {

case string:

    // After entering the branch, d is of type string

    fmt.Println(d + "str")

case int:

    // After entering the branch, d is of type int

    fmt.Println(d + 1)

}

// output

111str
  • Get the actual type of the interface through. (type). Remember that this method can only be used in switch statements, which is why I explain it here alone.

  • The fallthrough keyword cannot be used.

  • If you only judge the type, you do not need to use the d variable to accept.

summary

The interface knowledge of Go language is finished. It is very important to master it clearly. Now, do you know the essence of interface implementation?

To sum up, as long as the method is implemented, the interface is implemented. If the implemented method meets multiple interfaces, it will be implemented.

Tags: Go interface

Posted on Thu, 14 Oct 2021 21:04:06 -0400 by Daddy