Golang方法和接口

这里荒芜寸草不生 后来你来这里走了一遭 奇迹般万物生长 这里是我的心

What is a method?

A method is a function or a piece of code that is associated with an object.

Let’s say, you have a struct called “Car” and you create a method of the struct named “Drive”, then you declare an object from the struct and name it as “tesla”. So you can call the “Drive” method of your “tesla” to do whatever you programmed the method to do by using the properties or fields of your “tesla” to go the calculation.

What I just explained to you is only an example to make you get the concept of method. So now I will show you how to create your own methods and how to use them.

Usage of Method

Basically, all methods are associated with objects, means that you need to have at least one object in your code to declare any methods.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main

import "fmt"

type Car struct {
color string
distance int
}

func main() {
tesla := &Car{
distance: 0,
}
fmt.Println(tesla)
}

After you got your struct, you will be able to create your method

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package main

import "fmt"

type Car struct {
color string
distance int
}

func (c *Car) Drive(dist int) {
c.distance += dist
}

func main() {
tesla := &Car{
distance: 0,
}
fmt.Println("init distance:", tesla.distance)
tesla.Drive(10)
fmt.Println("1st drive distance:", tesla.distance)
}

/*
>>> OUTPUT <<<
init distance: 0
1st drive distance: 10
*/

A method is very similar to a function but the difference is that the method is associated with an object and can you the properties inside the object to calculate, just like the “Drive” method that uses “distance” property of “Car” to calculate new “distance”.

In case you need to expose some fields of your struct to other packages without directly exporting them, using methods as “Getter” is your best choice.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package main

import "fmt"

type Car struct {
color string
distance int
}

func (c *Car) Drive(dist int) {
c.distance += dist
}

func (c *Car) GetDistance() int {
return c.distance
}

func main() {
tesla := &Car{
distance: 0,
}
tesla.Drive(10)
fmt.Println("1st drive distance (without exporting):", tesla.GetDistance())
}

/*
>>> OUTPUT <<<
1st drive distance (without exporting): 10
*/

Setter is a way to set the value of unexported fields and getter can be used to setup default value of any field you want like the example below.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package main

import "fmt"

type Car struct {
color string
distance int
}

func (c *Car) Drive(dist int) {
c.distance += dist
}

// Getter
func (c *Car) GetDistance() int {
return c.distance
}

// Setter
func (c *Car) SetColor(color string) {
c.color = color
}

// Getter with default value
func (c *Car) GetColor() string {
if c.color == "" {
c.color = "white"
}
return c.color
}

func main() {
tesla := &Car{
distance: 0,
}
fmt.Println("get default color:", tesla.GetColor())
tesla.SetColor("silver")
fmt.Println("get new color:", tesla.GetColor())
}

/*
>>> OUTPUT <<<
get default color: white
get new color: silver
*/

Now you have already learnt how methods work and how to work with methods, but there is another feature in Golang that can enhance the power of structs and their methods. The feature is “Interface”.

What is an interface?

An interface is a set of method signature that an object can implement.

To make it more understandable, let’s say you have another struct named “Plane” and its methods: “Fly” and “GetDistance”. And you want to use it with “Car” struct by using the same logic to get their distance. An interface will help you for this kind of task.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package main

import "fmt"

// Car struct
type Car struct {
distance int
}

func (c *Car) Drive(dist int) {
c.distance += dist
}

func (c *Car) GetDistance() int {
return c.distance
}

// Plane struct
type Plane struct {
distance int
}

func (p *Plane) Fly(dist int) {
p.distance += dist
}

func (p *Plane) GetDistance() int {
return p.distance
}

// Use interface as argument
func GetVehicleDistance(i interface{}) int {
switch i.(type) {
case *Car:
c := i.(*Car) // Type assertion
return c.GetDistance()
case *Plane:
p := i.(*Plane) // Type assertion
return p.GetDistance()
}
return 0
}

func main() {
tesla := &Car{}
airbus := &Plane{}
tesla.Drive(10)
airbus.Fly(10000)
fmt.Println("tesla distance:", GetVehicleDistance(tesla))
fmt.Println("airbus distance:", GetVehicleDistance(airbus))
}

/*
>>> OUTPUT <<<
tesla distance: 10
airbus distance: 10000
*/

From the example above, you can use the same function with different parameter types by using interface as argument. And before you use the method from the interface, you need to do “type assertion” which is very important step for using interfaces.

Anyway the code can be improved and shorten by declaring an interface type with a set of method signature. For our case, it is “GetDistance” method. You will see how short it is.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package main

import "fmt"

// Car struct
type Car struct {
distance int
}

func (c *Car) Drive(dist int) {
c.distance += dist
}

func (c *Car) GetDistance() int {
return c.distance
}

// Plane struct
type Plane struct {
distance int
}

func (p *Plane) Fly(dist int) {
p.distance += dist
}

func (p *Plane) GetDistance() int {
return p.distance
}

// Declare interface type
type Vehicle interface {
GetDistance() int
}

// Use interface as argument
func GetVehicleDistance(i interface{}) int {
v := i.(Vehicle)
return v.GetDistance()
}

func main() {
tesla := &Car{}
airbus := &Plane{}
tesla.Drive(10)
airbus.Fly(10000)
fmt.Println("tesla distance:", GetVehicleDistance(tesla))
fmt.Println("airbus distance:", GetVehicleDistance(airbus))
}

/*
>>> OUTPUT <<<
tesla distance: 10
airbus distance: 10000
*/

However, don’t forget to validate your interface before using it because somehow it can be nil.

1
2
3
4
5
6
7
func GetVehicleDistance(i interface{}) int {
if i == nil { // validate interface before using
return 0
}
v := i.(Vehicle)
return v.GetDistance()
}

I hope you would get some point about implementing interface with multiple types, it will allow you to create only one logic for executing their method and make you life easier 😂.