OOP in Go: Interfaces
Full Stack, Blockchain Developer.
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.