This article words: 4376 words

Leoay, who writes technical, is serious too! New number for attention, like don’t get lost!

1

Hello, I’m leoay, long time no see again, it seems that the flag I set up in the last article was broken again, if you haven’t seen it yet, go to the last article and look at the flag I set up (talk about slices and arrays in Golang) and laugh at me quietly in your heart.

Sure enough, it is not easy to update and maintain two public accounts at the same time. Friends who are familiar with me should know that I almost always update the number of LEoay on a daily basis, so sometimes the technical number will be ignored. After all, I have to work like most people every day. Write the public number of time are I from their own rest and entertainment time to squeeze out.

Although it is difficult, BUT I always hope to do a good technical public account, because my main work is a programmer, so I hope to write how I regard technology as a technical person, as well as some of my learning technology experience; On the other hand, I also hope to give others some help through my sharing. However, at present, MY sharing and accumulation in technical aspects are not enough. Writing these is also a wake-up call for myself.

Ok, so without too much nonsense, let’s talk about today’s main topic, interfaces in the Go language.

2

So what is an interface?

In Go, an interface is a collection of methods, and when all the methods in the interface are defined in a type, we say that the interface is implemented.

This is much like interfaces mentioned in the object-oriented programming paradigm, such as Java.

An interface specifies the methods that a type has and determines how the type implements those methods.

For example, a washing machine can be thought of as an interface with washing and drying methods, so any type that provides washing and drying methods is said to implement a washing machine interface.

Let’s go into more detail in the code below:

3

Declare and implement an interface

package main

import (
	"fmt"
)

//interface definition
type WashingMachine interface {
	Cleaning() (string, error)
	Drying() (string, error)
}

type MyWashingMachine string

//MyWashingMachine implements WashingMachine
func (mwm MyWashingMachine) Cleaning(a) (string, error) {
	//do clean work
	return "Clean Ok!".nil
}

func (mwm MyWashingMachine) Drying(a) (string, error) {
	//do dry work
	return "Drying Ok!".nil
}

func main(a) {
	var v WashingMachine
	name := MyWashingMachine("Leoay Home")
	v = name
	resut, err := v.Cleaning()
	fmt.Println("result: ", resut, "err is: ", err)

	resut, err = v.Drying()
	fmt.Println("result: ", resut, "err is: ", err)
}
Copy the code

In the above code we created a WashingMachine interface type with two methods, Cleaning () and Drying()

Then we defined a MyWashingMachine type, then we wrote Cleaning() and Drying(), and made MyWashingMachine the receive type of the method.

At this point, we can say that MyWashingMachine implements the WashingMachine interface. This is quite different from Java, where we typically use the keyword implements to indicate that we are implementing an interface.

In Go, you just need this type to include all the methods in the interface.

So in the code below, we can call Cleaning() and Drying() directly with v because the WashingMachine already implements the methods in the WashingMachine.

At that point, we created an interface, okay, super simple.

In fact, if you learn more about the Go language, you will find that the interface is used so frequently in Go projects that it will appear in front of you without your knowing it, but it can be very confusing if you don’t know it.

4

Interface usage practices

From the example above, we know how to create and implement an interface, but we don’t really know how to use it in a real project.

Name := MyWashingMachine(“Leoay Home”)

So, what does this do?

What happens if we call the Cleaning() and Drying() functions directly with name?

At this time, the output is normal, but the interface is not used.

Next, I will use an example to illustrate the use of the interface.

We will write a simple program to calculate the total cost of the company based on an employee’s personal salary.

package main

import (  
    "fmt"
)

type SalaryCalculator interface {  
    CalculateSalary() int
}

type Permanent struct {  
    empId    int
    basicpay int
    pf       int
}

type Contract struct {  
    empId    int
    basicpay int
}

// The salary of regular employees is the sum of the base salary and bonus
func (p Permanent) CalculateSalary(a) int {  
    return p.basicpay + p.pf
}

// The salary of probationary employees is a separate base salary
func (c Contract) CalculateSalary(a) int {  
    return c.basicpay
}

// The total cost is calculated by iterating the SalaryCalculator slices and summing them up
func totalExpense(s []SalaryCalculator) {  
    expense := 0
    for _, v := range s {
        expense = expense + v.CalculateSalary()
    }
    fmt.Printf("Total monthly expenditure ¥% D", expense)
}

func main(a) {  
    pemp1 := Permanent{
        empId:    1,
        basicpay: 5000,
        pf:       20,
    }
    pemp2 := Permanent{
        empId:    2,
        basicpay: 6000,
        pf:       30,
    }
    cemp1 := Contract{
        empId:    3,
        basicpay: 3000,
    }
    employees := []SalaryCalculator{pemp1, pemp2, cemp1}
    totalExpense(employees)
}
Copy the code

From the code above we can see that we have declared a CalculateSalary () method in the SalaryCalculator interface.

There are two kinds of employees in the company, Permanent employee and probationary employee. The salary of Permanent employee includes basic salary and bonus, while that of probationary employee only includes basic salary.

However, we want to use only one method to calculate the employee’s salary, so we implement the SalaryCalculator interface with Permanent and Contract respectively, so that no matter what type of employee is, the salary can be calculated by CalculateSalary method.

Then we define a method totalExpense, which takes a SalaryCalculator slice as a parameter, and then passes all employee information to the method through the slice. Internally, the CalculateSalary method is called to calculate and sum each employee’s salary.

By executing the above code we can get the final output:

The total monthly expenditure is ¥14050

The big advantage of this is that totalExpense can be extended to any new employee type without any code changes.

Suppose the company adds Freelancer, a new type of employee with a different pay structure.

Freelancer can be passed to totalExpense only in the slice argument without a single line of code change to the totalExpense function.

This method will do what it’s supposed to do, and Freelancer will also implement the SalaryCalculator interface

Now let’s modify this program to add a new employee Freelancer whose salary is the product of revenue efficiency and total hours worked.

package main

import (  
    "fmt"
)

type SalaryCalculator interface {  
    CalculateSalary() int
}

type Permanent struct {  
    empId    int
    basicpay int
    pf       int
}

type Contract struct {  
    empId    int
    basicpay int
}

type Freelancer struct {  
    empId       int
    ratePerHour int
    totalHours  int
}

// The salary of regular employees is the sum of the base salary and bonus
func (p Permanent) CalculateSalary(a) int {  
    return p.basicpay + p.pf
}

// The salary of probationary employees is a separate base salary
func (c Contract) CalculateSalary(a) int {  
    return c.basicpay
}

// The salary of freelancers
func (f Freelancer) CalculateSalary(a) int {  
    return f.ratePerHour * f.totalHours
}

func totalExpense(s []SalaryCalculator) {  
    expense := 0
    for _, v := range s {
        expense = expense + v.CalculateSalary()
    }
    fmt.Printf("Total monthly expenditure ¥% D", expense)
}

func main(a) {  
    pemp1 := Permanent{
        empId:    1,
        basicpay: 5000,
        pf:       20,
    }
    pemp2 := Permanent{
        empId:    2,
        basicpay: 6000,
        pf:       30,
    }
    cemp1 := Contract{
        empId:    3,
        basicpay: 3000,
    }
    freelancer1 := Freelancer{
        empId:       4,
        ratePerHour: 70,
        totalHours:  120,
    }
    freelancer2 := Freelancer{
        empId:       5,
        ratePerHour: 100,
        totalHours:  100,
    }
    employees := []SalaryCalculator{pemp1, pemp2, cemp1, freelancer1, freelancer2}
    totalExpense(employees)
}
Copy the code

We added a Freelancer structure. And declares a CalculateSalary method implemented with Freelancer.

No other code changes are required in the new totalExpense method because the Freelancer structure also implements the SalaryCalculator interface.

Then we added a few employees of type Freelancer to the main method. After printing the program, the total monthly expenditure is ¥32450

5

Internal interface representation

You can think of an interface as internally represented by a tuple(type, value). Type is the underlying concrete type of the interface, and value is the value of the concrete type stored.

To better understand, let’s write a code demonstration:

package main

import (  
    "fmt"
)

type Worker interface {  
    Work()
}

type Person struct {  
    name string
}

func (p Person) Work(a) {  
    fmt.Println(p.name, "is working")}func describe(w Worker) {  
    fmt.Printf("Interface type %T value %v\n", w, w)
}

func main(a) {  
    p := Person{
        name: "Naveen",}var w Worker = p
    describe(w)
    w.Work()
}
Copy the code

As you can see from the code above, the Worker interface has a method, Work(), which is implemented by the Person struct type.

In main we defined p of type Person and assigned it to w of type Worker. Now w is of type Person and contains a variable name with the value Naveen.

The describe function prints the specific type and value of the Worker interface, resulting in:

Interface type main.Person value {Naveen}
Copy the code

Let’s take a closer look at how to get the underlying values of the interface.

6

Empty interface

An interface without methods is an empty interface, denoted by interface{}. Since an empty interface has no methods, all types implement an empty interface.

package main

import (  
    "fmt"
)

func describe(i interface{}) {  
    fmt.Printf("Type = %T, value = %v\n", i, i)
}

func main(a) {  
    s := "Hello World"
    describe(s)
    i := 55
    describe(i)
    strt := struct {
        name string
    }{
        name: "Naveen R",
    }
    describe(strt)
}
Copy the code

In the above code, because the describe(I interface{}) function takes an empty interface as a parameter, any type of parameter can be passed in.

So, in the example code above, we can pass strings, integers, and constructs into a describe function, and the result is output:

Type = string, value = Hello World  
Type = int, value = 55  
Type = struct { name string }, value = {Naveen R}  
Copy the code

7

Type assertion for the interface

Type assertions are used to get the underlying value of the interface.

I.(T) Is used to obtain the underlying value of the interface whose type is T.

Code is worth a million words, so I’m going to show you in code how type assertions are used. Okay

package main

import (  
    "fmt"
)

func assert(i interface{}) {  
    s := i.(int) // Get the underlying value of int from I
    fmt.Println(s)
}
func main(a) {  
    var s interface{} = 56
    assert(s)
}
Copy the code

The actual type of s is int. We use I.(int) to get the value of the int underlying I

So what happens if the actual type of the code above is not int? Look at the code below

package main

import (  
    "fmt"
)

func assert(i interface{}) {  
    s := i.(int) 
    fmt.Println(s)
}
func main(a) {  
    var s interface{} = "Steven Paul"
    assert(s)
}
Copy the code

In the above code, we pass a string to the assert function to retrieve an integer value. This code will panic and print the following message:

“Interface {} is string, not int.”

So what now? How can I avoid a crash?

We can actually solve it like this:

Because I.(T) will return an error exception, as long as we judge it, we can avoid crashing the program,

v, ok := i.(T)

If the actual type of I is T, then v is the value of I, ok is true, and the code works fine;

If the actual type of I was not T, v would return null, ok would be false, and the code would not crash.

So let’s make a simple change to the above code as follows:

package main

import (  
    "fmt"
)

func assert(i interface{}) {  
    v, ok := i.(int)
    fmt.Println(v, ok)
}

func main(a) {  
    var s interface{} = 56
    assert(s)
    var i interface{} = "Steven Paul"
    assert(i)
}
Copy the code

When “Steven Paul” is passed to the assert function, OK will be false because the concrete type of I is not int, so v has a value of 0. So the program will print:

56 true  0 false  
Copy the code

8

Type switch

The type switch is used to compare the actual type of the interface to the type specified in a case statement in a variety of cases.

Similar to switch case. The only difference is that case specifies the type instead of the value in a normal switch.

The syntax for type switches is similar to type assertions.

In the syntax for I.(T) type assertions, type T should replace type with the type switching keyword.

Here’s how it works in code:

package main

import (  
    "fmt"
)

func findType(i interface{}) {  
    switch i.(type) {
    case string:
        fmt.Printf("I am a string and my value is %s\n", i.(string))
    case int:
        fmt.Printf("I am an int and my value is %d\n", i.(int))
    default:
        fmt.Printf("Unknown type\n")}}func main(a) {  
    findType("Naveen")
    findType(77)
    findType(89.98)}Copy the code

In the above code, switch i.(type) specifies a type switch, and each case statement compares the actual type of I to the specified type. If any of the cases match, the corresponding statement is printed.

The final program output is as follows:

I am a string and my value is Naveen
I am an int and my value is 77
Unknown type
Copy the code

The 89.98 type is FLOAT64 and does not match anything, so the last line prints Unknown type.

You can also compare types to interfaces. If we have a type and that type implements an interface, we can compare the type to the interface it implements.

To make it clearer, let’s write a program that explains it in detail:

package main

import "fmt"

type Describer interface {  
    Describe()
}
type Person struct {  
    name string
    age  int
}

func (p Person) Describe(a) {  
    fmt.Printf("%s is %d years old", p.name, p.age)
}
func findType(i interface{}) {  
    switch v := i.(type) {
    case Describer:
        v.Describe()
    default:
        fmt.Printf("unknown type\n")}}func main(a) {  
    findType("Naveen")
    p := Person{
        name: "Naveen R",
        age:  25,
    }
    findType(p)
}
Copy the code

In the above program, the Person structure implements the Describer interface. We then compare type V with the Describer interface type using a case statement in the findType function.

And p is the Person type, so when we pass p to findType, v is Describer.

So the final program output is as follows:

unknown typeNaveen R is 25 years old
Copy the code

The above is today’s share, in fact, the interface was intended to be written in one article, but due to the length, so finally decided to split into two articles, save the second article to write another day.

I’m Leoay, growing a little bit with you every day!