OOP in Go: Interfaces
Table of contents
No headings in the article.
In the last article, we discussed Go structs, and how they can be used to mimic key OOP concepts like Encapsulation, Abstraction and Inheritance.
A Go interface is a collection of method signatures that define behaviors. An interface is defined using the type
keyword followed by the name of the new type and the keyword interface
.
type Ethnicity interface {
IsAsian() bool
IsIndian() bool
}
In the above example, we define an interface type Ethnicity
with method signature IsAsian() bool & IsIndian() bool. Any type that has a method with the same signatures is said to implement the interface.
Let us declare a Person struct, and implement IsAsian() and IsIndian() methods with the same arguments and return types.
type Person struct {
name string
country string
continent string
}
type Employee struct {
Person
salary int
}
func (p Person) IsAsian() bool {
return p.continent == "Asia"
}
func (p Person) IsIndian() bool {
return p.country == "India"
}
func main(){
p := Person{name: "John", country: "India", continent: "Asia"}
c.IsAsian()
c.IsIndian()
}
Since type Person
has methods(receiver functions) with the same name and similar return types as defined in the Ethnicity
interface, it implements the Ethnicity
interface.
We could also implement the interface by simply declaring a Person
variable and assigning it to an Ethnicity
variable, like so:
func main() {
p := Person{name: "John", country: "India", continent: "Asia"}
var e Ethnicity = p
e.IsAsian()
e.IsIndian()
}
Go also allows the embedding of interfaces in structs, which can be helpful in inheriting behaviors and defining new structs with additional behaviors.
type Person struct {
name string
age int
netIncome int
country string
continent string
Ethnicity
}
type WealthManager interface{
NetWorth() int
IsWealthy() bool
}
type SuperRich struct{
Person
WealthManager
}
func (p Person) NetWorth() int {
return p.netIncome - p.debt
}
func (p Person) IsWealthy() bool {
return p.NetWorth() > 1
}
func (s SuperRich) IsSuperRich() {
fmt.Printf("%v is from %v and, is super rich with a networth of %v", s.Person.name, s.Person.Country, s.Person.NetWorth())
}
func main() {
fmt.Println("Hello World!")
p := Person{name: "John", age: 30, netIncome: 50000, debt: 10000, country: "India", continent: "Asia"}
var isSuperRich SuperRich = SuperRich{Person: p, WealthManager: p}
isSuperRich.IsSuperRich()
}
Here, we have Person
implementing the WealthManger
interface by having two receiver functions IsWealthy() and NetWorth().
The SuperRich
struct combines the properties and behaviors of Person
and the WealthManager
, and has additional behavior in the form of IsSuperRich()
method.
In the main function, we have the isSuperRich variable, where we assign new Person
p twice. We can assign p to WealthManager
as the Person
struct implements the WealthManager
interface.
Interfaces with no method signatures are not bound to any behavior and hence can hold values of any type. This property allows us to achieve Polymorphism.
type x interface{}
x = "hello"
fmt.Println(x)
x = 42
fmt.Println(x)
x = true
fmt.Println(x)
Hence, we can see that interfaces in Go can allow us to mimic OOP properties like Inheritance using nesting and Polymorphism using empty interfaces. Interfaces are used to define behaviors that a type must implement. This allows functions to accept any type that implements the interface, making the code more flexible and easier to maintain.
In the upcoming article, we shall discuss receiver functions in Go.