Go language interfaces

below:

Go's interface design is simple yet powerful, and is an important tool for achieving polymorphism and decoupling.

Interfaces allow us to bind different types to a set of common methods, thereby enabling polymorphism and flexible design.

Interface Features

Implicit implementation:

  • Go has no keyword to explicitly declare that a type implements an interface.
  • As long as a type implements all the methods required by an interface, the type is automatically considered to implement that interface.

Interface type variables:

  • An interface variable can store any value that implements the interface.
  • An interface variable actually consists of two parts:
    • Dynamic type: stores the actual value type.
    • Dynamic value: stores the concrete value.

Zero value interface:

  • The zero value of an interface isnil。
  • The value of an uninitialized interface variable isnil, and it does not contain any dynamic type or value.

Empty interface:

  • Defined asinterface{}, can represent any type.

Common uses of interfaces

  1. PolymorphismDifferent types implementing the same interface achieve polymorphic behavior.
  2. DecouplingDefining dependencies through interfaces reduces coupling between modules.
  3. Generalization: Using the empty interfaceinterface{}represents any type.

Interface definition and implementation

Interface definitions use the keywordinterface, which contains method declarations.

Example

/* Define interface */
type interface_name interface {
   method_name1 [return_type]
   method_name2 [return_type]
   method_name3 [return_type]
   ...
   method_namen [return_type]
}

/* Define struct */
type struct_name struct {
   /* variables */
}

/* Implement interface methods */
func (struct_name_variable struct_name) method_name1() [return_type] {
   /* Method implementation */
}
...
func (struct_name_variable struct_name) method_namen() [return_type] {
   /* Method implementation*/
}

Define a simple interface:

type Shape interface {
    Area() float64
    Perimeter() float64
}
  • Shapeis an interface that defines two methods:AreaandPerimeter。
  • Any type that implements these two methods is considered to have implemented the interface.ShapeInterface.

Implement interface:Types implement an interface by implementing all the methods required by the interface.

Example

package main

import (
        "fmt"
        "math"
)

// Define interface
type Shape interface {
        Area() float64
        Perimeter() float64
}

// Define a struct
type Circle struct {
        Radius float64
}

// Circle implements Shape interface
func (c Circle) Area() float64 {
        return math.Pi * c.Radius * c.Radius
}

func (c Circle) Perimeter() float64 {
        return 2 * math.Pi * c.Radius
}

func main() {
        c := Circle{Radius: 5}
        var s Shape = c // Interface variables can store types that implement the interface
        fmt.Println("Area:", s.Area())
        fmt.Println("Perimeter:", s.Perimeter())
}

Execute the above code, the output is:

Area: 78.53981633974483
Perimeter: 31.41592653589793

Empty interface

Empty interfaceinterface{}It is a special interface in Go, representing the superset of all types.

  • Any type implements the empty interface.
  • It is often used in scenarios that need to store data of any type, such as generic containers, generic parameters, etc.

Example

package main

import "fmt"

func printValue(val interface{}) {
        fmt.Printf("Value: %v, Type: %T\n", val, val)
}

func main() {
        printValue(42)         // int
        printValue("hello")    // string
        printValue(3.14)       // float64
        printValue([]int{1, 2}) // slice
}

Execute the above code, the output is:

Value: 42, Type: int
Value: hello, Type: string
Value: 3.14, Type: float64
Value: [1 2], Type: []int

Type assertion

Type assertion is used to extract the underlying value from an interface type.

Basic syntax:

value := iface.(Type)
  • ifaceis an interface variable.
  • Typeis the concrete type to be asserted.
  • If the type does not match, it triggerspanic。

Example

package main

import "fmt"

func main() {
        var i interface{} = "hello"
        str := i.(string) // Type assertion
        fmt.Println(str)  // Output: hello
}

type assertion with a check

To avoid panic, a checked type assertion can be used:

value, ok := iface.(Type)
  • okIt is a boolean value indicating whether the assertion was successful.
  • If the assertion fails,valueis the zero value,okisfalse。

Example

package main

import "fmt"

func main() {
        var i interface{} = 42
        if str, ok := i.(string); ok {
                fmt.Println("String:", str)
        } else {
                fmt.Println("Not a string")
        }
}

Execute the above code, the output is:

Not a string

type switch

Type switch is a syntax construct in Go used to execute different logic based on the concrete type of an interface variable.

Example

package main

import "fmt"

func printType(val interface{}) {
        switch v := val.(type) {
        case int:
                fmt.Println("Integer:", v)
        case string:
                fmt.Println("String:", v)
        case float64:
                fmt.Println("Float:", v)
        default:
                fmt.Println("Unknown type")
        }
}

func main() {
        printType(42)
        printType("hello")
        printType(3.14)
        printType([]int{1, 2, 3})
}

Execute the above code, the output is:

Integer: 42
String: hello
Float: 3.14
Unknown type

Interface composition

Interfaces can describe more complex behaviors through nesting and composition.

Example

package main

import "fmt"

type Reader interface {
        Read() string
}

type Writer interface {
        Write(data string)
}

type ReadWriter interface {
        Reader
        Writer
}

type File struct{}

func (f File) Read() string {
        return "Reading data"
}

func (f File) Write(data string) {
        fmt.Println("Writing data:", data)
}

func main() {
        var rw ReadWriter = File{}
        fmt.Println(rw.Read())
        rw.Write("Hello, Go!")
}

dynamic value and dynamic type

An interface variable actually contains two parts:

  1. Dynamic type: the concrete type stored in the interface variable.
  2. Dynamic value: the value of the concrete type.

Example of dynamic value and dynamic type:

Example

package main

import "fmt"

func main() {
        var i interface{} = 42
        fmt.Printf("Dynamic type: %T, Dynamic value: %v\n", i, i)
}

Execute the above code, the output is:

Dynamic type: int, Dynamic value: 42

Zero value of interface

The zero value of an interface is nil.

When both the dynamic type and dynamic value of an interface variable are nil, the interface variable is nil.

Interface zero value example:

Example

package main

import "fmt"

func main() {
        var i interface{}
        fmt.Println(i == nil) // Output: true
}

Practice examples

The following two examples demonstrate the use of interfaces:

Example 1

package main

import (
    "fmt"
)

type Phone interface {
    call()
}

type NokiaPhone struct {
}

func (nokiaPhone NokiaPhone) call() {
    fmt.Println("I am Nokia, I can call you!")
}

type IPhone struct {
}

func (iPhone IPhone) call() {
    fmt.Println("I am iPhone, I can call you!")
}

func main() {
    var phone Phone

    phone = new(NokiaPhone)
    phone.call()

    phone = new(IPhone)
    phone.call()

}

In the above example, we defined an interface.Phone, the interface contains a methodcall(). Then wemainIn the function, aPhonetype variable is defined, and values are assigned to it respectively asNokiaPhoneandIPhone. Then callcall()method, the output is as follows:

I am Nokia, I can call you!
I am iPhone, I can call you!

The second interface example:

Example

package main

import "fmt"

type Shape interface {
    area() float64
}

type Rectangle struct {
    width  float64
    height float64
}

func (r Rectangle) area() float64 {
    return r.width * r.height
}

type Circle struct {
    radius float64
}

func (c Circle) area() float64 {
    return 3.14 * c.radius * c.radius
}

func main() {
    var s Shape

    s = Rectangle{width: 10, height: 5}
    fmt.Printf("Rectangle area: %f\n", s.area())

    s = Circle{radius: 3}
    fmt.Printf("Circle area: %f\n", s.area())
}

In the above example, we defined a Shape interface, which defines a method area() that returns an area value of type float64. Then, we defined two structs, Rectangle and Circle, which respectively implement the area() method of the Shape interface. In the main() function, we first defined a variable s of type Shape, then assigned instances of Rectangle and Circle to it respectively, and via the area() method calculated their areas and printed them out. The output is as follows:

矩形面积: 50.000000
圆形面积: 28.260000

It should be noted that an interface type variable can store a value of any type that implements the interface. In the example, we assigned instances of both Rectangle and Circle types to the variable s of type Shape, and called their area calculation methods via the area() method.

other extensions