Interfaces

After covering methods, we must cover interfaces, which make use of methods to produce efficient and scalable code in the Go language.

An interface can be very simply described as a Go type that hosts a collection of methods.

Here is a simple example:

type MyInterface interface{
GetName()string
GetAge()int
}

The preceding interface defines two methods—GetName() and GetAge().

Earlier, we attached two methods with the same signature to a type called Person:

type Person struct{
name string
age int
}
func (p Person) GetName()string{
return p.name
}
func (p Person) GetAge()int{
return p.age
}

In Go, an interface can be implemented by other types, like Go structs. When a Go type implements an interface, a value of the interface type can then hold that Go type data. We'll see what that means very shortly.

A very special feature in Go is the fact that for a type to implement or inherit an interface, the type only needs to implement the methods of said interface.

In other words, the Person struct type from the preceding piece of code implements the myInterface interface type. This is due to the fact that the Person type implements GetName() and GetAge(), which are the same methods that were defined by myInterface.

So, what does it mean when Person implements MyInterface?

It means that we can do something like this:

var myInterfaceValue MyInterface
var p = Person{}
p.name = "Jack"
p.age = 39
// some code
myInterfaceValue = p
myInterfaceValue.GetName() //returns: Jack
myInterfaceValue.GetAge() //returns: 39

We can also do this:

func main(){
p := Person{"Alice",26}
printNameAndAge(p)
}

func PrintNameAndAge(i MyInterface){
fmt.Println(i.GetName(),i.GetAge())
}

Interfaces are used quite a bit in APIs and in scalable software. They allow you to build software with flexible functionality. Here is a trivial example of how it helps you build flexible software.

Let's say we want to create a new person type that appends a title to the name:

type PersonWithTitle {
name string
title string
age int
}

func (p PersonWithTitle) GetName()string{
//This code returns <title> <space> <name>
return p.title + " " + p.name
}

func (p PersonWithTitle) GetAge() int{
return p.age
}

The preceding type also implements MyInterface, which means we can do this:

func main(){
pt := PersonWithTitle{"Alice","Dr.",26}
printNameAndAge(pt)
}

func PrintNameAndAge(i MyInterface){
fmt.Println(i.GetName(),i.GetAge())
}

The PrintNameAndAge() function signature will not need to change, since it relies on the interface instead of the concrete type. However, the behavior will differ a bit since we changed the concrete struct type from Person to PersonWithTitle. This ability allows you to write flexible APIs and packages that don't need to change whenever you need to add more concrete types to your code.

There are cases where you might want to get back the concrete type value from an interface value. Go includes a feature called type assertion that can be used for just that. Here is the most useful form of type assertion:

person, ok := myInterfaceValue.(Person)

The preceding code assumes that we are inside a function block. If myInterfaceValue does not hold a value of type Person, the preceding code will return an empty struct for the first return, and false for the second return. Therefore, ok will be false, whereas Person will be empty.

On the other hand, if myInterfaceValue holds a value of type Person, then ok will become true, and the Person variable will hold the data that's retrieved from myInterfaceValue.

Now, let's explore how to add logic to our code, by covering conditional statements and loops.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.189.178.53