[Go]5.Types, Struct and Interface

Type

Type alias is for better readabilty, they will be replaced with the original type at compile time. We can define type alias in Go:

type myByte = byte
var b myByte
fmt.Println("%T\n", b)  // uint8

We can also create a type from a existing type. Here we define a completely new type:

type myInt int // no "=" here
var i myInt
fmt.Println("%T\n", i)  // main.myInt

We can define a function type:

type handle func(str string)

Struct

Struct in Go behaves like struct in C, which can have data attributes but we can't define methods within the declaration of the struct type:

type Course struct {
    price int
    name string
    url string
}

Initialization

Key-value pattern:

var c Course = Course {
    price: 100,
    name: "csci5103",
    url: "https://libguides.umn.edu/CSCI/5103", 
}

In-order pattern:

var c Course = Course {
    100, 
    "csci5103",
    "https://libguides.umn.edu/CSCI/5103"
}

We also have the following two ways to define a struct pointer:

var c *Course = &Course {
    100, 
    "csci5103",
    "https://libguides.umn.edu/CSCI/5103"
}
var c *Course = new(Course)

Inheritance

Go doesn't support OOP, which means that we can't have native support for inheritance like programming languages that strongly support inheritance, like Java, Python. But we can use composition in Go to simulate the effect of inheritance:

type Teacher struct {
    name string
    age int
    title string
}


type Course struct {
    teacher Teacher
    price int
    name string
    url string
}

func getInfo(c Course){
    fmt.Println(c.teacher.name, c.teacher.age)
}

You may argue that the above method is far from ideal. We still need to first specify the attribute name of Teacher in order to visit its fields. But actually we can omit the name of `Teacher inorder to achieve a more similar effects as inheritance:

type Teacher struct {
    name string
    age int
    title string
}


type Course struct {
    Teacher
    price int
    name string
    url string
}

func getInfo(c Course){
    fmt.Println(c.name, c.age)
}

For the above code snippet, you will notice that both Course and Teacher structs have fields name, so what attirbute will we visit when we trying to call c.name? The answer is the outer struct's. If you want to visit the anonymous attribute's name, you can use c.Teacher.name to access it. This is all achieved using syntax sugar.

Tagging

In Go's struct, we can assign tags to fields in a struct which can be used as meta-data for other methods to treat differently:

type Info struct {
    Name string
    Age  int   `json:"age,string"` 
    // This means this field will be renamed to json
    // and the type will be converted to string 
    // when it's parsing into json.
    Sex  string
}

This can be combined with the reflect pacakge together to achieve every flexible logic when developing.

Methods

We can't define any methods within the struct declaration, but we can still bound some methods to an exisiting struct:

type Circle struct {
    x int
    y int
    Radius int
}

func (c Circle) Area() float64 {
    return math.Pi * float64(c.Radius) * float64(c.Radius)
}

func main() {
    var c = Circle {Radius: 50}
    fmt.Println(c.Area(), c.Circumference())
    // We can use the same way to call method for one struct or its corresponding pointer
    var pc = &c
    fmt.Println(pc.Area(), pc.Circumference())
}

Interface

We can define interfaces in Go, which contain a bunch of methods to describe the behavior of this interface. As long as on struct implement all the interfaces, it can be treated captaible with this interface:

import (
    "fmt"
    "math"
)

type geometry interface {
    area() float64
    perim() float64
}

type rect struct {
    width, height float64
}
type circle struct {
    radius float64
}

func (r rect) area() float64 {
    return r.width * r.height
}
func (r rect) perim() float64 {
    return 2*r.width + 2*r.height
}

func (c circle) area() float64 {
    return math.Pi * c.radius * c.radius
}
func (c circle) perim() float64 {
    return 2 * math.Pi * c.radius
}

func measure(g geometry) {
    fmt.Println(g)
    fmt.Println(g.area())
    fmt.Println(g.perim())
}
func main() {
    r := rect{width: 3, height: 4}
    c := circle{radius: 5}

    measure(r) 
    measure(c)

    // Both shapes can be measured
}

Inheritance

We can also achieve inheritance in interfaces, and this is also achieved through composition:

package main

import (
    "fmt"
)

type Pythoner struct {

}

func (p Pythoner) coding() {
    fmt.Println("I can code in Python")
}

func (p Pythoner) manage() {
    fmt.Println("I can manage People")
}

type Programmer interface {
    coding() 
}

type Manager interface {
    manage()
}

type ProductManager interface {
    Programmer
    Manager
}

func checkManager(m ProductManager) {
    m.coding()
    m.manage()
}

func main() {
    var p = Pythoner{}
    checkManager(p)
}

// Output:
// I can code in Python
// I can manage People

Reference

  1. Go by Example: Interfaces