#2.6 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 more work, we add one more method Sing()
to them, also add BorrowMoney()
to Student and SpendSalary()
to Employee.
Now Student has three methods called SayHi()
, Sing()
, BorrowMoney()
, and Employee has SayHi()
, Sing()
and SpendSalary()
.
This combination of methods is called an interface, and is implemented by Student and Employee. So Student and Employee implement the interface: SayHi()
, 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()
.
###Type of Interface An interface defines a set of methods, so if a type implements all the methods we say that it implements the interface.
type Human struct {
name string
age int
phone string
}
type Student struct {
Human
school string
loan float32
}
type Employee struct {
Human
company string
money float32
}
func (h *Human) SayHi() {
fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}
func (h *Human) Sing(lyrics string) {
fmt.Println("La la, la la la, la la la la la...", lyrics)
}
func (h *Human) Guzzle(beerStein string) {
fmt.Println("Guzzle Guzzle Guzzle...", beerStein)
}
// Employee overloads Sayhi
func (e *Employee) SayHi() {
fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,
e.company, e.phone) //Yes you can split into 2 lines here.
}
func (s *Student) BorrowMoney(amount float32) {
s.loan += amount // (again and again and...)
}
func (e *Employee) SpendSalary(amount float32) {
e.money -= amount // More vodka please!!! Get me through the day!
}
// define interface
type Men interface {
SayHi()
Sing(lyrics string)
Guzzle(beerStein string)
}
type YoungChap interface {
SayHi()
Sing(song string)
BorrowMoney(amount float32)
}
type ElderlyGent interface {
SayHi()
Sing(song string)
SpendSalary(amount float32)
}
We know that an interface can be implemented by any type, and one type can implement many interfaces at the same time.
Note that any type implements the empty interface interface{}
because it doesn't have any methods and all types have zero methods as 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 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.
package main
import "fmt"
type Human struct {
name string
age int
phone string
}
type Student struct {
Human
school string
loan float32
}
type Employee struct {
Human
company string
money float32
}
func (h Human) SayHi() {
fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}
func (h Human) Sing(lyrics string) {
fmt.Println("La la la la...", lyrics)
}
func (e Employee) SayHi() {
fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,
e.company, e.phone) //Yes you can split into 2 lines here.
}
// Interface Men implemented by Human, Student and Employee
type Men interface {
SayHi()
Sing(lyrics string)
}
func main() {
mike := Student{Human{"Mike", 25, "222-222-XXX"}, "MIT", 0.00}
paul := Student{Human{"Paul", 26, "111-222-XXX"}, "Harvard", 100}
sam := Employee{Human{"Sam", 36, "444-222-XXX"}, "Golang Inc.", 1000}
Tom := Employee{Human{"Sam", 36, "444-222-XXX"}, "Things Ltd.", 5000}
// define interface i
var i Men
//i can store Student
i = mike
fmt.Println("This is Mike, a Student:")
i.SayHi()
i.Sing("November rain")
//i can store Employee
i = Tom
fmt.Println("This is Tom, an Employee:")
i.SayHi()
i.Sing("Born to be wild")
// slice of Men
fmt.Println("Let's use a slice of Men and see what happens")
x := make([]Men, 3)
// these three elements are different types but they all implemented interface Men
x[0], x[1], x[2] = paul, sam, mike
for _, value := range x {
value.SayHi()
}
}
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 implemented an empty interface. It's very useful when we want to store all types at some point, and is similar to void* in C.
// define a as empty interface
var a interface{}
var i int = 5
s := "Hello world"
// a can store value of any type
a = i
a = s
If a function uses an empty interface as its argument type, it can accept any type; if a function uses empty as its return value type, it can return any type.
###Method arguments of an interface Any variable that can be used in an interface, so we can think about how can we use this feature to pass any type of variable to the function.
For example, we use fmt.Println a lot, but have you ever noticed that it accepts any type of arguments? Looking at the open source code of fmt, we see the following definition.
type Stringer interface {
String() string
}
This means any type that implements interface Stringer can be passed to fmt.Println as an argument. Let's prove it.
package main
import (
"fmt"
"strconv"
)
type Human struct {
name string
age int
phone string
}
// Human implemented fmt.Stringer
func (h Human) String() string {
return "Name:" + h.name + ", Age:" + strconv.Itoa(h.age) + " years, Contact:" + h.phone
}
func main() {
Bob := Human{"Bob", 39, "000-7777-XXX"}
fmt.Println("This Human is : ", Bob)
}
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.
fmt.Println("The biggest one is", boxes.BiggestsColor().String())
fmt.Println("The biggest one is", boxes.BiggestsColor())
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 that I'm going to tell 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 the 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.
package main
import (
"fmt"
"strconv"
)
type Element interface{}
type List []Element
type Person struct {
name string
age int
}
func (p Person) String() string {
return "(name: " + p.name + " - age: " + strconv.Itoa(p.age) + " years)"
}
func main() {
list := make(List, 3)
list[0] = 1 // an int
list[1] = "Hello" // a string
list[2] = Person{"Dennis", 70}
for index, element := range list {
if value, ok := element.(int); ok {
fmt.Printf("list[%d] is an int and its value is %d\n", index, value)
} else if value, ok := element.(string); ok {
fmt.Printf("list[%d] is a string and its value is %s\n", index, value)
} else if value, ok := element.(Person); ok {
fmt.Printf("list[%d] is a Person and its value is %s\n", index, value)
} else {
fmt.Println("list[%d] is of a different type", index)
}
}
}
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.
package main
import (
"fmt"
"strconv"
)
type Element interface{}
type List []Element
type Person struct {
name string
age int
}
func (p Person) String() string {
return "(name: " + p.name + " - age: " + strconv.Itoa(p.age) + " years)"
}
func main() {
list := make(List, 3)
list[0] = 1 //an int
list[1] = "Hello" //a string
list[2] = Person{"Dennis", 70}
for index, element := range list {
switch value := element.(type) {
case int:
fmt.Printf("list[%d] is an int and its value is %d\n", index, value)
case string:
fmt.Printf("list[%d] is a string and its value is %s\n", index, value)
case Person:
fmt.Printf("list[%d] is a Person and its value is %s\n", index, value)
default:
fmt.Println("list[%d] is of a different type", index)
}
}
}
One thing you should remember is that element.(type)
cannot be used outside of switch
body, which means in that case you have you use pattern comma-ok
.
###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 follows the same rules as anonymous fields. More specifically, if an interface has another interface as the embedded interface, it will have all the methods that the embedded interface has.
We can see source file in container/heap
has one definition as follows.
type Interface interface {
sort.Interface // embedded sort.Interface
Push(x interface{}) //a Push method to push elements into the heap
Pop() interface{} //a Pop elements that pops elements from the heap
}
We see that sort.Interface
is an embedded interface, so the above Interface has three methods that in sort.Interface
implicitly.
type Interface interface {
// Len is the number of elements in the collection.
Len() int
// Less returns whether the element with index i should sort
// before the element with index j.
Less(i, j int) bool
// Swap swaps the elements with indexes i and j.
Swap(i, j int)
}
Another example is the io.ReadWriter
in package io
.
// io.ReadWriter
type ReadWriter interface {
Reader
Writer
}
###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 to use reflect. First, we need to convert an interface to reflect types (reflect.Type or reflect.Value, this depends on the situation).
t := reflect.TypeOf(i) // get meta-data in type i, and use t to get all elements
v := reflect.ValueOf(i) // get actual value in type i, and use t to change its value
After that, we convert reflect types to get values that we need.
var x float64 = 3.4
v := reflect.ValueOf(x)
fmt.Println("type:", v.Type())
fmt.Println("kind is float64:", v.Kind() == reflect.Float64)
fmt.Println("value:", v.Float())
Finally, if we want to change a value that is from reflect 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.
var x float64 = 3.4
v := reflect.ValueOf(x)
v.SetFloat(7.1)
Instead, we must use the following code to change value from reflect types.
var x float64 = 3.4
p := reflect.ValueOf(&x)
v := p.Elem()
v.SetFloat(7.1)
I just talked about basic knowledge about reflection, you must pratice more to understand more.
##Links
- Directory
- Previous section: Object-oriented
- Next section: Concurrency