DEV Community

nadirbasalamah
nadirbasalamah

Posted on • Updated on

Golang tutorial - 7 struct and interface

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)
}
Enter fullscreen mode Exit fullscreen mode

Output:

value of a:  42
memory address of a:  0xc000014080
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

Output:

value of a:  42
memory address of a:  0xc000014080
value of b: 0xc000014080
value of b using * operator:  42
Enter fullscreen mode Exit fullscreen mode

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.
Pointer illustration

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)
Enter fullscreen mode Exit fullscreen mode

Output:

value of a:  45
value of b:  45
Enter fullscreen mode Exit fullscreen mode

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)
Enter fullscreen mode Exit fullscreen mode

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)
}

Enter fullscreen mode Exit fullscreen mode

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}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

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()
Enter fullscreen mode Exit fullscreen mode

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()
}

Enter fullscreen mode Exit fullscreen mode

Output:

Hello, my name is:  Enki Gilbert
Enter fullscreen mode Exit fullscreen mode

The alternative syntax of using a defined struct can be done by ignoring the field name.

//instantiate the person
p1 := person{"Enki Gilbert", 42}
Enter fullscreen mode Exit fullscreen mode

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"},
}
Enter fullscreen mode Exit fullscreen mode

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()

}

Enter fullscreen mode Exit fullscreen mode

Output:

Hello, my name is:  Enki Gilbert
Enter fullscreen mode Exit fullscreen mode

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"}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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)
}

Enter fullscreen mode Exit fullscreen mode

Output:

the area:  144
Enter fullscreen mode Exit fullscreen mode

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
}

Enter fullscreen mode Exit fullscreen mode

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)
}

Enter fullscreen mode Exit fullscreen mode

Output:

the area:  144
the area:  625
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

Output:

the area:  144
the area:  144
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

Output:

the area:  144
Enter fullscreen mode Exit fullscreen mode

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)