Structures

About

Go isn't a object-oriented language like Java or C#. It doesn't have objects, inheritance, polymorphism nor overloading.

What it does have are structures, which can be associated with methods.

type Foo struct {
    Foo string
    Bar string
}

Functions on Structures

func (f *Foobar) Baz() {
    s.Bar = "Baz"
}

In the above code, we say that the type *Foobar is the receiver of the Baz method.

foobar := &Boobar{"Foo", "Bar"}
foobar.Baz()
fmt.Println(foobar.Bar) // Baz

Constructors

Go doesn't have a constructor method like other programming languages like Java do.

Instead, we create a function that returns an instance of the type.

func NewFoobar(foo string, bar string) *Foobar {
    return &Foobar{
        Foo: foo,
        Bar: bar,
    }
}

Our function doesn't need to return a pointer. Below is also valid.

func NewFoobar(foo string, bar string) Foobar {
    return Foobar{
        Foo: foo,
        Bar: bar,
    }
}

Go has a build-in new function which is used to allocate the memory required by a type.

The result of new(X) is the same as &X{}:

foobar := new(Foobar)
foobar := &Foobar{} // same

But the latter is often preferred because we can specify fields to initialize in a more readable way.

foobar := new(Foobar)
foobar.Foo = "foo"
foobar.Bar = "bar"

// v.s.

foobar := &Foobar {
    Foo: "foo",
    Bar: "bar",
}

Composition

Go supports composition: the act of including one structure into another.

This is called a trait or a mixin in some languages.

For example, Java doesn't have an explicit composition mechanism, so a mixin would be written like this:

public class Person {
    private String name;
    
    public String getName() {
        return this.name;
    }
}

public class Saiyan {
    // Saiyan is said to have a person
    private Person person;
    
    // we forward the call to person
    public String getName() {
        return this.person.getName();
    }
    // ...
}

This can be tedious because every method of Person needs to be duplicated in Saiyan.

Go avoids tediousness.

type Person struct {
    Name string
}

func (p *Person) Introduce() {
    fmt.Printf("Hi, I'm %s\n", p.Name)
}

type Saiyan struct {
    *Person
    Power int
}

goku := &Saiyan{
    Person: &Person{"Goku"},
    Power: 9001,
}
goku.Introduce()

// The compiler did give it a field name.
// Both are perfectly valid.
fmt.Println(goku.Name)
fmt.Println(goku.Person.Name)

Overloading

Go doesn't support overloading. You'll see and write a lot of functions like Load, LoadById, LoadByName and so on.

However, because implicit composition is really just a compiler trick, we can "overwrite" the functions of a composed type.

For example, our Saiyan structure can have its own Introduct function.

func (s *Saiyan) Introduce() {
    fmt.Printf("Hi, I'm %s. Ya!\n", s.Name)
}

The composed version is always available via s.Person.Introduce().

goku.Introduce() // Hi, I'm Goku. Ya!
goku.Person.Introduce() // Hi, I'm Goku

REF

Many examples here are excerpted from The Little Go Book.

Last updated