OOP in Go
Object-oriented programming (OOP) is a common pattern in many programming languages. Object-oriented programming has many advantages like reusability, polymorphism, and encapsulation. The concept of OOP can be implemented using Go but with a different approach. Some concepts in OOP like class, interface, and methods are supported in Go but there is no type hierarchy. In Golang's official documentation in the FAQ section there is an interesting answer to the question "Is Go an object-oriented language?" that you can check here
Pointer
Pointer is an addressing mechanism that is supported in Go. In pointer, there are two operators called addressing or pointing into certain variables (&
) and dereference (*
).
Let's take a look at this example:
func main() {
a := 42
//prints out the value of a
fmt.Println("value of a: ", a)
//prints out the memory address that pointing to "a" variable
fmt.Println("memory address of a: ", &a)
}
Output:
value of a: 42
memory address of a: 0xc000014080
Based on that code, the &
operator addresses certain variables.
With a pointer, a variable is capable of addressing another variable. For example, a b
variable can be addressed into another variable (in this code the variable is called a
). Here is the code example based on that case.
func main() {
a := 42
//prints out the value of a
fmt.Println("value of a: ", a)
//prints out the memory address that pointing to "a" variable
fmt.Println("memory address of a: ", &a)
b := &a
//prints out the memory address of "b" variable
fmt.Println("value of b:", b)
//to prints out the value of b, use * (dereference) operator
fmt.Println("value of b using * operator: ", *b)
}
Output:
value of a: 42
memory address of a: 0xc000014080
value of b: 0xc000014080
value of b using * operator: 42
Notice that the value of b and the value of a are equal because the b
variable contains the memory address that same as a
variable.
The illustration looks like this.
If the value of a
changes, the value of b
also changes.
//changes the value of "a"
a = 45
fmt.Println("value of a: ", a)
fmt.Println("value of b: ", *b)
Output:
value of a: 45
value of b: 45
The new()
function can be used to create a new pointer based on the given type. This is the basic structure of using new()
function.
new(data_type)
This is an example.
package main
import (
"fmt"
)
type product struct {
name string
price float64
}
func main() {
// create a pointer of integer
intPointer := new(int)
// assign value
*intPointer = 24
// create a pointer of struct
productPointer := new(product)
// assign fields
productPointer.name = "apple"
productPointer.price = 8000
fmt.Printf("the type of intPointer is %T\n", intPointer)
fmt.Printf("the value of intPointer: %v\n", *intPointer)
fmt.Printf("the type of productPointer is %T\n", productPointer)
fmt.Printf("the value of productPointer: %v\n", *productPointer)
}
Output
the type of intPointer is *int
the value of intPointer: 24
the type of productPointer is *main.product
the value of productPointer: {apple 8000}
struct
Class is a concept that is mainly used in OOP. In Go, there is a similar concept in class called struct
. To declare a struct
can be done by using this syntax:
type structName struct {
//field name type
member int
member2 string
member3 []string
}
Here is an example of using struct
. This struct illustrates a person that has a behavior to say something.
// declare a struct called person
type person struct {
name string
age int
}
// declare a method say() with type person as receiver
func (p *person) say() {
fmt.Println("Hello, my name is: ", p.name)
}
To use the defined struct, assign defined struct into variable like this:
//instantiate the person
p1 := person{name: "Enki Gilbert", age: 42}
//call a method say()
p1.say()
Here it is the complete code:
package main
import "fmt"
// declare a struct called person
type person struct {
name string
age int
}
// declare a method say() with type person as receiver
func (p *person) say() {
fmt.Println("Hello, my name is: ", p.name)
}
func main() {
//instantiate the person
p1 := person{name: "Enki Gilbert", age: 42}
// call a method say()
p1.say()
}
Output:
Hello, my name is: Enki Gilbert
The alternative syntax of using a defined struct can be done by ignoring the field name.
//instantiate the person
p1 := person{"Enki Gilbert", 42}
The anonymous struct is also available in Go.
s1 := struct{
//declare some fields
field1 int
field2 []string
}{
//instantiate directly
field1: 12,
field2: []string{"hi","mate"},
}
Composition
The inheritance feature isn't supported in Go, we can use the composition mechanism instead. Here is the example of struct
composition:
package main
import "fmt"
// declare a struct called person
type person struct {
name string
age int
}
// composition example
// declare a struct called manager that includes another struct called person
type manager struct {
person
team string
}
// declare a method say() with type person as receiver
func (p *person) say() {
fmt.Println("Hello, my name is: ", p.name)
}
func main() {
//instantiate the person
p1 := person{"Enki Gilbert", 42}
//instantiate the manager and assign person field by using p1 variable
m1 := manager{person: p1, team: "Racing Team Solvalou"}
//the say() method is still available
m1.say()
}
Output:
Hello, my name is: Enki Gilbert
Based on that code, we declare a struct called manager
that composes another struct called person
. The say()
method is still available to the manager
struct because it composes another struct (person
) that has a say()
method.
The alternative syntax of instantiating the struct that has a struct composition:
//instatiate the person struct into person field directly
m1 := manager{person: person{"Enki Gilbert", 42}, team: "Racing Team Solvalou"}
Interface
Interface is a powerful concept in programming. The interface is similar to struct but it only contains some abstract methods. In Go, the interface defines a generic abstraction of behaviors. To declare an interface can be done by using this syntax:
//type name interface
type name interface {
//declare some methods
method1()
method2() int
}
Here is an example of using the interface.
package main
import "fmt"
// declare a rectangle struct
type rectangle struct {
length int
width int
}
// declare an interface with area() as a member
type shape interface {
area() int
}
// declare a method area()
// the rectangle struct implements area() method in shape interface
func (r *rectangle) area() int {
return r.length * r.width
}
// declare a method with type shape as a parameter
func info(s shape) {
fmt.Println("the area: ", s.area())
}
func main() {
r1 := rectangle{12, 12}
//r1 is a rectangle type. rectangle implements all methods in shape interface.
info(&r1)
}
Output:
the area: 144
Based on that example, we declare a struct called rectangle
and an interface called shape
. The rectangle
implements an area()
method in the shape
interface. There is a method called info()
with shape
type as a parameter. The info()
method's parameter is accessible for every struct that implements all methods in the shape
interface.
If there is another struct, in this case, called square
. The info()
method is also available because the square
struct also implements all methods in the shape
interface.
type square struct {
side int
}
//the square struct implements area() method in shape interface
func (s *square) area() int {
return s.side * s.side
}
Here is the complete example:
package main
import (
"fmt"
)
// declare a rectangle struct
type rectangle struct {
length int
width int
}
// declare a square struct
type square struct {
side int
}
// declare an interface with area() as a member
type shape interface {
area() int
}
// declare a method area()
// the rectangle struct implements area() method in shape interface
func (r *rectangle) area() int {
return r.length * r.width
}
// the square struct implements area() method in shape interface
func (s *square) area() int {
return s.side * s.side
}
//declare a method with type shape as a parameter
/**
anything that implements all methods in shape interface is considered as a shape in general.
for this case the rectangle and square is a shape because implements all methods in shape interface
**/
func info(s shape) {
fmt.Println("the area: ", s.area())
}
func main() {
r1 := rectangle{12, 12}
info(&r1)
s1 := square{25}
info(&s1)
}
Output:
the area: 144
the area: 625
The composition of the interface is also available. The example can be checked in io
package here.
Method sets
A method set is a function that is associated with a certain type. For example, the area()
is a function associated with the shape
type. If the receiver of the method set is a value, then the method can be used by a value or a pointer of a value.
For example, this area()
method is available for the value and pointer of type shape
(in this example is a rectangle).
//shape interface
type shape interface {
area() int
}
func (r rectangle) area() int {
return r.length * r.width
}
//used in info() function
func info(s shape) {
fmt.Println("the area: ", s.area())
}
func main() {
r1 := rectangle{12, 12}
//with value
info(r1)
//with pointer
info(&r1)
}
Output:
the area: 144
the area: 144
If the receiver of the method set is a pointer, then the method is only available with the pointer.
Example:
//the receiver is a pointer
func (r *rectangle) area() int {
return r.length * r.width
}
func info(s shape) {
fmt.Println("the area: ", s.area())
}
func main() {
r1 := rectangle{12, 12}
//with value, throw an error
// info(r1)
//with pointer
info(&r1)
}
Output:
the area: 144
Notes
- Learn more about struct in here
- Learn more about pointers in here
- Learn more about method sets in here
I hope this article is helpful to learn the Go programming language. If you have any thoughts or feedback, you can write it in the discussion section below.
Top comments (0)