Go -- Interface Type

Posted by Ammar on Tue, 21 Sep 2021 00:24:07 +0200

1. Interfaces

  • An interface is a type (defined by the type keyword)
  • An interface defines the behavior specification of an object. Only the specification is defined and not implemented. Specific objects implement the specifications in detail.
  • Interfaces limit the methods that structures must implement

1. Interface type

  • In the Go language, an interface is a type, an abstract type.
  • An interface is a collection of methods, a reflection of duck-type programming. What an interface does is just define a protocol (rule), which I call a washing machine whenever a machine has laundry and shake-dry functions. It doesn't care about attributes (data), it only about behavior (method).

2. Why use interfaces

type Cat struct{}

func (c Cat) Say() string { return "cat" }

type Dog struct{}

func (d Dog) Say() string { return "Wangwang" }

func main() {
	c := Cat{}
	fmt.Println("cat:", c.Say())
	d := Dog{}
	fmt.Println("Dog:", d.Say())
}

3. Definition of interface

  • The Go language promotes Interface-oriented programming.
  • Interface Definition Format:
type Interface name interface{
	Method 1(Parameter 1,Parameter 2···)(Return value 1,Return Value 2···)
	Method 2(Parameter 1,Parameter 2···)(Return value 1,Return Value 2···)
	····
	}
	
# Interface name: Use type to define an interface as a custom type name. When naming an interface in Go Language, an er is usually added after the word. For example, Writer is the interface for writing operations, Stringer is the interface for string functions, etc. Interface names are best used to highlight the type meaning of the interface.
# Method Name: This method can be accessed by code other than the package in which the interface is located when the method name is capitalized and the interface type name is capitalized.
# Parameter list, return value list: parameter variable names in parameter list and return value list can be omitted.

Example: The interface name of the Write method, which we can usually define as Writer

type writer interface{
    Write([]byte) error
}

4. Conditions for implementing interfaces

  • If a variable implements all the methods specified in the interface, then it implements the interface and can be called a variable of this interface type (that is, calling the variable, which can be defined as an interface type).
  • To implement an interface, you must satisfy all the methods in the interface and the defined method format

Example:

type spake interface {
	spake()
}

// Defined hit function
func hit(animal spake) {
	animal.spake()
}

// Define a domain structure
type dog struct {
	name string
}
// Define a cat structure
type cat struct {
	name string
}
// dog implements the spake interface
func (d dog) spake() {
	fmt.Printf("%s Wangwang~\n", d.name)
}
// cat implements the spake interface
func (c cat) spake() {
	fmt.Printf("%s cat~\n", c.name)
}

func main() {
	var d1 dog
	var c1 cat
  
	var s1 spake
	s1 = d1       // Structural instances that satisfy all methods in an interface can be assigned to variables of the interface type
	hit(s1)
	var s2 spake
	s2 = c1
	hit(s2)
}

Example 2:

type animal interface {
	move()
	eat(string)
}
type cat struct {
	name string
	feet int8
}
type chicken struct {
	feet int8
}

func (c chicken) move() {
	fmt.Println("Chicken moves")
}
func (c chicken) eat(f string) {
	fmt.Printf("Chicken will eat%s", f)
}
func (c cat) move() {
	fmt.Println("Cats can run")
}
func (c cat) eat(f string) {
	fmt.Printf("Cat eats%s", f)
}

func main() {
	var a1 animal
	bc := cat{
		"White Cat",
		4,
	}
	a1 = bc
	a1.eat("Small fish")
}

5. Interface type variables

  • Interface type variables can store all instances that implement the interface. For example, in the example above, variables of type Sayer can store variables of type Dom and cat.
func main() {
	var x Sayer // Declare a Sayer-type variable x
	a := cat{}  // Instantiate a cat
	b := dog{}  // Instantiate a dog
	x = a       // cat instances can be directly assigned to x
	x.say()     // cat
	x = b       // dog instances can be directly assigned to x
	x.say()     // Wangwang
}

Plus: Look at the code below and enjoy the great use of _here

// From gin framework routergroup.go
type IRouter interface{ ... }

type RouterGroup struct { ... }

var _ IRouter = &RouterGroup{}  // Ensure that RouterGroup implements the interface IRouter

6. The difference between value receiver and pointer receiver in implementing interfaces

6.1. Implementing interfaces using value receivers

  • When the recipient of a method uses a value, the interface can receive either a pointer or a value

Example: Using the method defined by the value receiver, we found that both the cat structure and the *cat pointer structure can assign variables to the interface. (Because the grammar sugar that evaluates pointer-type variables in the Go language, the cat pointer C2 automatically evaluates *c2 internally.)

type animal interface {
	eat(string)
}
type cat struct {
	name string
	feet int8
}

// Method implements all methods of an interface using a value receiver
func (c cat) eat(f string) {
	fmt.Printf("Cat eats%s\n", f)
}

func main() {
	var a1, a2 animal
	c1 := cat{"White Cat", 4}  // cat type
	c2 := &cat{"Black cat", 4} // *cat type
	a1 = c1
	a2 = c2
	fmt.Printf("a1:%#v  a2:%#v", a1, a2)
	a1.eat("fish")
	a2.eat("Mouse")
}

Result

a1:main.cat{name:"White Cat", feet:4}  a2:&main.cat{name:"Black cat", feet:4}
Cats eat fish
 Cats eat mice

6.2. Implementing interfaces using pointer receivers

  • If the method defined is pointer receiver, then interface variables can only receive pointers.
  • Usually the method of incoming is pointer recipient, so this should be noted

Example: The recipient of the eat method is of type *cat, so it is not possible to pass in an instance of type cat to a3, where the interface variable A3 can only store values of type *cat.

type animal interface {
	eat(string)
}
type cat struct {
	name string
	feet int8
}

// Method implements all methods of an interface using a value receiver
func (c *cat) eat(f string) {
	fmt.Printf("Cat eats%s\n", f)
}

func main() {
	var a3 animal
	c3 := cat{"blue cat", 4}
	a3 = &c3
	fmt.Printf("a3:%#V\n ", a3) // A3 is pointer type
	a3.eat("naughty")
}

Result

a3:&main.cat{name:"blue cat", feet:4}
Cats eat mischief

7. Relationships between interfaces and types

  • Multiple types can implement the same interface
  • One type implements multiple interfaces
  • The method of an interface does not necessarily need to be fully implemented by one type. The method of an interface can be implemented by embedding other types or structures in the type.

Example 1: One type can implement multiple interfaces, and multiple types can implement the same interface

type eater interface {
	eat(string)
}
type mover interface {
	move()
}

type cat struct{}
type dog struct{}

// One type implements multiple interfaces, cat implements eater and mover interfaces
func (c cat) eat(f string) {
	fmt.Printf("Cat eats%s\n", f)
}
func (c cat) move() {
	fmt.Println("Cats can run")
}

// Multiple types can implement the same interface, and cat and domain types both implement eater interfaces
func (d dog) eat() {
	fmt.Println("Fall")
}

Example 2: The method of an interface does not necessarily need to be fully implemented by one type. The method of an interface can be implemented by embedding other types or structures in the type.

type eater interface {
	eat(string)
}

type cat struct{}

// One type implements multiple interfaces, cat implements eater and mover interfaces
func (c cat) eat(f string) {
	fmt.Printf("Cat eats%s\n", f)
}

// Define a Garfield cat structure that inherits cat properties
type jiafeimao struct {
	cat
}

func main() {
	j1 := jiafeimao{}
	var e1 eater = j1  // jiafeumao references the cat structure and can also implement interfaces
	e1.eat("fish")
}

8. Interface Nesting

  • New interfaces can be created by nesting between interfaces.
type eater interface {
	eat(string)
}

type mover interface {
	mover()
}

// Nesting mover interface and eater interface to form a new interface animal
type animal interface{
	mover
	eater
	sleep()
}

9. Empty interface

9.1. Definition of an empty interface

  • An empty interface is an interface that does not define any method. Therefore, any type implements an empty interface.
  • Variables of an empty interface type can store any type of variable.
  • The format is as follows: interface {} // empty interfaces are usually defined in this manner and are generally not named

Example:

// Empty interfaces can be named or unnamed, as f1 and f2 do
type kong interface{}
func f1(x kong) {
	fmt.Println(x)
}
func f2(x interface{}) {
	fmt.Println(x)
}

func main() {
	var (
		a int    = 1
		b string = "abc"
	)
	f1(a)
	f1(b)
	f2(a)
	f2(b)
}

Result

1
abc
1
abc

9.2. Application of empty interface

  • An empty interface is used as a parameter of a function: an empty interface is used to implement a function parameter that can accept any type.
    Example
func f(a ...interface{}) {
	for _, i := range a {
		fmt.Printf("%v ", i)
	}
}

func main() {
	f(1, "abc", []string{"a", "b", "c"})
}

Output Results

1 abc [a b c]
  • Empty interface as map value
    Example: When defining a map, slice, the types of elements passed in are variable and empty interfaces can be used
func main() {
	var m1 = make(map[string]interface{}, 10)
	m1["name"] = "Xiaowang"                     // Character string
	m1["age"] = 20                        // int
	m1["married"] = true                  // Boolean
	m1["hobby"] = []string{"say", "sing", "jump"} // Array, [...] denotes automatic calculation of capacity size
	fmt.Println(m1)
}

Output Results

map[age:20 hobby:[Rap and dance] married:true name:Xiaowang]

10. Type Assertions

  • An empty interface can store any type of value, but if a program wants to know what type of data is coming in, a type assertion (used to determine the type of variable we're passing in with an empty interface) is required.

10.1, Interface value

  • The value of an interface (referred to as interface value) consists of a specific type and a specific type of value.
  • These two parts are called the dynamic type and dynamic value of the interface (that is, the data stored by the interface has two parts, the incoming data type and the value of the data).

Example:

var w io.Writer
w = os.Stdout
w = new(bytes.Buffer)
w = nil

graphic

10.2. How to determine the interface type

  • Judgement grammar format: x.(T), the grammar returns two parameters, the first parameter is the variable after x is converted to T type, the second value is a Boolean value, true means the assertion succeeds, false means the assertion fails.
    • x: variable of type interface {}
    • T: Indicates the possible type of assertion x.

Example 1: The incoming A is of type int, and when a.(string) is output, an error will be raised, and the incoming a of type int does not satisfy a.(string)

func assign(a interface{}) {
	fmt.Println(a.(string))
	}
func main(){
    assign(12) // Direct error at this time
}

Example 2: Judging directly by using if (this kind of comparison is rigid, writing types one by one is inconvenient)

func assign(a interface{}) {
	fmt.Printf("%T\n", a)
	k, ok := a.(string)   // Returns the value of the interface and the Boolean value of the judgment
	if !ok {
		fmt.Println("This is not a string type")
	} else {
		fmt.Println("This is a string type", k)
	}
}

func main() {
	assign("abc")
}

Output Results

string
 This is a string type abc

Example 2: Multi-type comparison using switch

func assign(a interface{}) {
	switch v := a.(type) {
	case string:
		fmt.Printf("%v,yes string type\n", v)
	case int:
		fmt.Printf("%v,yes int type\n", v)
	case bool:
		fmt.Printf("%v,yes bool type\n", v)
	default:
		break
	}
}

func main() {
	assign(123)
	assign("abc")
	assign(true)
}

Output Results

123,yes int type
abc,yes string type
true,yes bool type

Topics: Java Go