interface
One of the subtlest design features in Go are interfaces. After reading this section, you will likely be impressed by their implementation.
What is an interface?
In short, an interface is a set of methods that we use to define a set of actions.
Like the examples in previous sections, both Student and Employee can SayHi()
, but they don't do the same thing.
Let's do some more work. We'll add one more method Sing()
to them, along with the BorrowMoney()
method to Student and the SpendSalary()
method to Employee.
Now, Student has three methods called SayHi()
, Sing()
and BorrowMoney()
, and Employee has SayHi()
, Sing()
and SpendSalary()
.
This combination of methods is called an interface and is implemented by both Student and Employee. So, Student and Employee implement the interface: SayHi()
and Sing()
. At the same time, Employee doesn't implement the interface: SayHi()
, Sing()
, BorrowMoney()
, and Student doesn't implement the interface: SayHi()
, Sing()
, SpendSalary()
. This is because Employee doesn't have the method BorrowMoney()
and Student doesn't have the method SpendSalary()
.
Types of Interface
An interface defines a set of methods, so if a type implements all the methods we say that it implements the interface.
We know that an interface can be implemented by any type, and one type can implement many interfaces simultaneously.
Note that any type implements the empty interface interface{}
because it doesn't have any methods and all types have zero methods by default.
Value of interface
So what kind of values can be put in the interface? If we define a variable as a type interface, any type that implements the interface can be assigned to this variable.
Like the above example, if we define a variable "m" as interface Men, then any one of Student, Human or Employee can be assigned to "m". So we could have a slice of Men, and any type that implements interface Men can assign to this slice. Be aware however that the slice of interface doesn't have the same behavior as a slice of other types.
file: code/Interface/InterfaceValue/value.go
An interface is a set of abstract methods, and can be implemented by non-interface types. It cannot therefore implement itself.
Empty interface
An empty interface is an interface that doesn't contain any methods, so all types implement an empty interface. This fact is very useful when we want to store all types at some point, and is similar to void* in C.
If a function uses an empty interface as its argument type, it can accept any type; if a function uses empty interface as its return value type, it can return any type.
Method arguments of an interface
Any variable can be used in an interface. So how can we use this feature to pass any type of variable to a function?
For example we use fmt.Println a lot, but have you ever noticed that it can accept any type of argument? Looking at the open source code of fmt, we see the following definition.
This means any type that implements interface Stringer can be passed to fmt.Println as an argument. Let's prove it.
file: code/Interface/Stringer/stringer.go
Looking back to the example of Box, you will find that Color implements interface Stringer as well, so we are able to customize the print format. If we don't implement this interface, fmt.Println prints the type with its default format.
Attention: If the type implemented the interface error
, fmt will call error()
, so you don't have to implement Stringer at this point.
Type of variable in an interface
If a variable is the type that implements an interface, we know that any other type that implements the same interface can be assigned to this variable. The question is how can we know the specific type stored in the interface. There are two ways which I will show you.
Assertion of Comma-ok pattern
Go has the syntax value, ok := element.(T)
. This checks to see if the variable is the type that we expect, where "value" is the value of the variable, "ok" is a variable of boolean type, "element" is the interface variable and the T is the type of assertion.
If the element is the type that we expect, ok will be true, false otherwise.
Let's use an example to see more clearly.
file: code/Interface/Person/person.go
It's quite easy to use this pattern, but if we have many types to test, we'd better use switch
.
switch test
Let's use switch
to rewrite the above example.
file: code/Interface/switch/switch.go
One thing you should remember is that element.(type)
cannot be used outside of the switch
body, which means in that case you have to use the comma-ok
pattern .
Embedded interfaces
The most beautiful thing is that Go has a lot of built-in logic syntax, such as anonymous fields in struct. Not suprisingly, we can use interfaces as anonymous fields as well, but we call them Embedded interfaces
. Here, we follow the same rules as anonymous fields. More specifically, if an interface has another interface embedded within it, it will behave as if it has all the methods that the embedded interface has.
We can see that the source file in container/heap
has the following definition:
We see that sort.Interface
is an embedded interface, so the above Interface has the three methods contained within the sort.Interface
implicitly.
Another example is the io.ReadWriter
in package io
.
Reflection
Reflection in Go is used for determining information at runtime. We use the reflect
package, and this official article explains how reflect works in Go.
There are three steps involved when using reflect. First, we need to convert an interface to reflect types (reflect.Type or reflect.Value, this depends on the situation).
After that, we can convert the reflected types to get the values that we need.
Finally, if we want to change the values of the reflected types, we need to make it modifiable. As discussed earlier, there is a difference between pass by value and pass by reference. The following code will not compile.
Instead, we must use the following code to change the values from reflect types.
Links
Last updated