Interfaces - I
Welcome to tutorial no. 18 in Golang tutorial series. This is the first part in our 2 part interface tutorial.
What is an interface?
In Go, an interface is a set of method signatures. When a type provides definition for all the methods in the interface, it is said to implement the interface. It is much similar to the OOP world. Interface specifies what methods a type should have and the type decides how to implement these methods.
For example WashingMachine can be an interface with method signatures Cleaning() and Drying(). Any type which provides definition for Cleaning() and Drying() methods is said to implement the WashingMachine interface.
Declaring and implementing an interface
Let’s dive right into a program that creates an interface and implements it.
1package main
2
3import (
4 "fmt"
5)
6
7//interface definition
8type VowelsFinder interface {
9 FindVowels() []rune
10}
11
12type MyString string
13
14//MyString implements VowelsFinder
15func (ms MyString) FindVowels() []rune {
16 var vowels []rune
17 for _, rune := range ms {
18 if rune == 'a' || rune == 'e' || rune == 'i' || rune == 'o' || rune == 'u' {
19 vowels = append(vowels, rune)
20 }
21 }
22 return vowels
23}
24
25func main() {
26 name := MyString("Sam Anderson")
27 var v VowelsFinder
28 v = name // possible since MyString implements VowelsFinder
29 fmt.Printf("Vowels are %c", v.FindVowels())
30
31}
Line no. 8 of the program above creates a interface type named VowelsFinder
which has one method FindVowels() []rune
.
In the next line, a type MyString
is created.
In line no. 15 we add the method FindVowels() []rune
to the receiver type MyString
. Now MyString
is said to implement the interface VowelsFinder
. This is quite different from other languages like Java where a class has to explicitly state that it implements an interface using the implements
keyword. This is not needed in Go and Go interfaces are implemented implicitly if a type contains all the methods declared in the interface.
In line no.28, we assign name
which is of type MyString
to v of type VowelsFinder
. This is possible since MyString
implements the VowelsFinder
interface. v.FindVowels()
in the next line calls the FindVowels method on MyString
type and prints all the vowels in the string Sam Anderson
. This program outputs
Vowels are [a e o]
Congrats! You have created and implemented your first interface.
Practical use of an interface
The above example taught us how to create and implement interfaces, but it didn’t really show the practical use of an interface. Instead of v.FindVowels()
, if we used name.FindVowels()
in the program above, it would have also worked and there would have been no use for interface.
Now let’s look at a practical use of interface.
We will write a simple program that calculates the total expense for a company based on the individual salaries of the employees. For brevity, we have assumed that all expenses are in USD.
1package main
2
3import (
4 "fmt"
5)
6
7type SalaryCalculator interface {
8 CalculateSalary() int
9}
10
11type Permanent struct {
12 empId int
13 basicpay int
14 pf int
15}
16
17type Contract struct {
18 empId int
19 basicpay int
20}
21
22//salary of permanent employee is the sum of basic pay and pf
23func (p Permanent) CalculateSalary() int {
24 return p.basicpay + p.pf
25}
26
27//salary of contract employee is the basic pay alone
28func (c Contract) CalculateSalary() int {
29 return c.basicpay
30}
31
32/*
33total expense is calculated by iterating through the SalaryCalculator slice and summing
34the salaries of the individual employees
35*/
36func totalExpense(s []SalaryCalculator) {
37 expense := 0
38 for _, v := range s {
39 expense = expense + v.CalculateSalary()
40 }
41 fmt.Printf("Total Expense Per Month $%d", expense)
42}
43
44func main() {
45 pemp1 := Permanent{
46 empId: 1,
47 basicpay: 5000,
48 pf: 20,
49 }
50 pemp2 := Permanent{
51 empId: 2,
52 basicpay: 6000,
53 pf: 30,
54 }
55 cemp1 := Contract{
56 empId: 3,
57 basicpay: 3000,
58 }
59 employees := []SalaryCalculator{pemp1, pemp2, cemp1}
60 totalExpense(employees)
61
62}
Line no. 7 of the above program declares the SalaryCalculator
interface with a single method CalculateSalary() int
.
We have two kinds of employees in the company, Permanent
and Contract
defined by structs in line no. 11 and 17. The salary of permanent employees is the sum of the basicpay
and pf
whereas for contract employees it’s just the basic pay basicpay
. This is expressed in the corresponding CalculateSalary
methods in line. no 23 and 28 respectively. By declaring this method, both Permanent
and Contract
structs now implement the SalaryCalculator
interface.
The totalExpense
function declared in line no.36 expresses the beauty of interfaces. This method takes a slice of SalaryCalculator interface []SalaryCalculator
as a parameter. In line no. 59 we pass a slice that contains both Permanent
and Contract
types to the totalExpense
function. The totalExpense
function calculates the expense by calling the CalculateSalary
method of the corresponding type. This is done in line. no 39.
The program outputs
Total Expense Per Month $14050
The biggest advantage of this is that totalExpense
can be extended to any new employee type without any code changes. Let’s say the company adds a new type of employee Freelancer
with a different salary structure. This Freelancer
can just be passed in the slice argument to totalExpense
without even a single line of code change to the totalExpense
function. This method will do what it’s supposed to do as Freelancer
will also implement the SalaryCalculator
interface :).
Let’s modify this program and add the new Freelancer
employee. Salary for the Freelancer is the product of rate per hour and total no of hours worked.
1package main
2
3import (
4 "fmt"
5)
6
7type SalaryCalculator interface {
8 CalculateSalary() int
9}
10
11type Permanent struct {
12 empId int
13 basicpay int
14 pf int
15}
16
17type Contract struct {
18 empId int
19 basicpay int
20}
21
22type Freelancer struct {
23 empId int
24 ratePerHour int
25 totalHours int
26}
27
28//salary of permanent employee is sum of basic pay and pf
29func (p Permanent) CalculateSalary() int {
30 return p.basicpay + p.pf
31}
32
33//salary of contract employee is the basic pay alone
34func (c Contract) CalculateSalary() int {
35 return c.basicpay
36}
37
38//salary of freelancer
39func (f Freelancer) CalculateSalary() int {
40 return f.ratePerHour * f.totalHours
41}
42
43/*
44total expense is calculated by iterating through the SalaryCalculator slice and summing
45the salaries of the individual employees
46*/
47func totalExpense(s []SalaryCalculator) {
48 expense := 0
49 for _, v := range s {
50 expense = expense + v.CalculateSalary()
51 }
52 fmt.Printf("Total Expense Per Month $%d", expense)
53}
54
55func main() {
56 pemp1 := Permanent{
57 empId: 1,
58 basicpay: 5000,
59 pf: 20,
60 }
61 pemp2 := Permanent{
62 empId: 2,
63 basicpay: 6000,
64 pf: 30,
65 }
66 cemp1 := Contract{
67 empId: 3,
68 basicpay: 3000,
69 }
70 freelancer1 := Freelancer{
71 empId: 4,
72 ratePerHour: 70,
73 totalHours: 120,
74 }
75 freelancer2 := Freelancer{
76 empId: 5,
77 ratePerHour: 100,
78 totalHours: 100,
79 }
80 employees := []SalaryCalculator{pemp1, pemp2, cemp1, freelancer1, freelancer2}
81 totalExpense(employees)
82
83}
We have added the Freelancer
struct in line no. 22 and declared the CalculateSalary
method in line no. 39. No other code change is required in the totalExpense
method since Freelancer
struct also implements the SalaryCalculator
interface. We added a couple of Freelancer
employees in the main
method. This program prints,
Total Expense Per Month $32450
Interface internal representation
An interface can be thought of as being represented internally by a tuple (type, value)
. type
is the underlying concrete type of the interface and value
holds the value of the concrete type.
Let’s write a program to understand better.
1package main
2
3import (
4 "fmt"
5)
6
7type Worker interface {
8 Work()
9}
10
11type Person struct {
12 name string
13}
14
15func (p Person) Work() {
16 fmt.Println(p.name, "is working")
17}
18
19func describe(w Worker) {
20 fmt.Printf("Interface type %T value %v\n", w, w)
21}
22
23func main() {
24 p := Person{
25 name: "Naveen",
26 }
27 var w Worker = p
28 describe(w)
29 w.Work()
30}
Worker interface has one method Work()
and Person struct type implements that interface. In line no. 27, we assign the variable p
of type Person
to w
which is of type Worker
. Now the concrete type of w
is Person
and it contains a Person
with name
field Naveen
. The describe
function in line no.17 prints the value and concrete type of the interface. This program outputs
Interface type main.Person value {Naveen}
Naveen is working
We discuss more on how to extract the underlying value of the interface in the upcoming sections.
Empty interface
An interface that has zero methods is called an empty interface. It is represented as interface{}
. Since the empty interface has zero methods, all types implement the empty interface.
1package main
2
3import (
4 "fmt"
5)
6
7func describe(i interface{}) {
8 fmt.Printf("Type = %T, value = %v\n", i, i)
9}
10
11func main() {
12 s := "Hello World"
13 describe(s)
14 i := 55
15 describe(i)
16 strt := struct {
17 name string
18 }{
19 name: "Naveen R",
20 }
21 describe(strt)
22}
In the program above, in line no.7, the describe(i interface{})
function takes an empty interface as an argument and hence any type can be passed.
We pass string
, int
and struct
to the describe
function in line nos. 13, 15 and 21 respectively. This program prints,
Type = string, value = Hello World
Type = int, value = 55
Type = struct { name string }, value = {Naveen R}
Type assertion
Type assertion is used to extract the underlying value of the interface.
i.(T) is the syntax which is used to get the underlying value of interface i
whose concrete type is T
.
A program is worth a thousand words 😀. Let’s write one for type assertion.
1package main
2
3import (
4 "fmt"
5)
6
7func assert(i interface{}) {
8 s := i.(int) //get the underlying int value from i
9 fmt.Println(s)
10}
11func main() {
12 var s interface{} = 56
13 assert(s)
14}
The concrete type of s
in line no. 12 is int
. We use the syntax i.(int)
in line no. 8 to fetch the underlying int value of i. This program prints 56
.
What will happen if the concrete type in the above program is not int? Well, let’s find out.
1package main
2
3import (
4 "fmt"
5)
6
7func assert(i interface{}) {
8 s := i.(int)
9 fmt.Println(s)
10}
11func main() {
12 var s interface{} = "Steven Paul"
13 assert(s)
14}
In the program above we pass s
of concrete type string
to the assert
function which tries to extract a int value from it. This program will panic with the message panic: interface conversion: interface {} is string, not int
.
To solve the above problem, we can use the syntax
v, ok := i.(T)
If the concrete type of i
is T
then v
will have the underlying value of i
and ok
will be true.
If the concrete type of i
is not T
then ok
will be false and v
will have the zero value of type T
and the program will not panic.
1package main
2
3import (
4 "fmt"
5)
6
7func assert(i interface{}) {
8 v, ok := i.(int)
9 fmt.Println(v, ok)
10}
11func main() {
12 var s interface{} = 56
13 assert(s)
14 var i interface{} = "Steven Paul"
15 assert(i)
16}
When Steven Paul
is passed to the assert
function, ok
will be false since the concrete type of i
is not int
and v
will have the value 0 which is the zero value of int
. This program will print,
56 true
0 false
Type switch
A type switch is used to compare the concrete type of an interface against multiple types specified in various case statements. It is similar to switch case. The only difference being the cases specify types and not values as in normal switch.
The syntax for type switch is similar to Type assertion. In the syntax i.(T)
for Type assertion, the type T
should be replaced by the keyword type
for type switch. Let’s see how this works in the program below.
1package main
2
3import (
4 "fmt"
5)
6
7func findType(i interface{}) {
8 switch i.(type) {
9 case string:
10 fmt.Printf("I am a string and my value is %s\n", i.(string))
11 case int:
12 fmt.Printf("I am an int and my value is %d\n", i.(int))
13 default:
14 fmt.Printf("Unknown type\n")
15 }
16}
17func main() {
18 findType("Naveen")
19 findType(77)
20 findType(89.98)
21}
In line no. 8 of the above program, switch i.(type)
specifies a type switch. Each of the case statements compare the concrete type of i
to a specific type. If any case matches, the corresponding statement is printed. This program outputs,
I am a string and my value is Naveen
I am an int and my value is 77
Unknown type
89.98 in line no. 20 is of type float64
and does not match any of the cases and hence Unknown type
is printed in the last line.
It is also possible to compare a type to an interface. If we have a type and if that type implements an interface, it is possible to compare this type with the interface it implements.
Let’s write a program for more clarity.
1package main
2
3import "fmt"
4
5type Describer interface {
6 Describe()
7}
8type Person struct {
9 name string
10 age int
11}
12
13func (p Person) Describe() {
14 fmt.Printf("%s is %d years old", p.name, p.age)
15}
16
17func findType(i interface{}) {
18 switch v := i.(type) {
19 case Describer:
20 v.Describe()
21 default:
22 fmt.Printf("unknown type\n")
23 }
24}
25
26func main() {
27 findType("Naveen")
28 p := Person{
29 name: "Naveen R",
30 age: 25,
31 }
32 findType(p)
33}
In the program above, the Person
struct implements the Describer
interface. In the case statement in line no. 19, v
is compared to the Describer
interface type. p
implements Describer
and hence this case is satisfied and Describe()
method is called.
This program prints
unknown type
Naveen R is 25 years old
This brings us to the end of Interfaces Part I. We will continue our discussion about interfaces in Part II. I hope you liked this tutorial. Please leave your feedback and comments. Please consider sharing this tutorial on twitter and LinkedIn. Have a good day.
Next tutorial - Interfaces - II