I would call GoLang a data-oriented language, not an object-oriented language, although you can make some object-oriented patterns with it. For example, Golang doesn't have classes, inheritance and method overriding. Another thing it doesn't have is objects. Go has three classes of types:
- Built-in.
- Struct.
- Reference.
The struct type is the closest thing we have to an object from other languages such as Ruby or Python. We use structs when the built-in types (strings, numerics, booleans) are not enough to represent the mental model we want to achieve. A struct allows you to define the data that you need to describe a "thing" this could be a dog, a mobile phone, a user, etc.
Go works differently when instantiating a struct; for example, with Python, the most basic implementation of an object would be like this.
class Person:
def __init__(self, name, age, music):
self.name = name
self.age = age
self.favourite_music = music
The syntax to create an object is this:
person = Person("John", 36, ["rock", "punk", "classical"])
In case we want to only set the name, we will get the following error:
person = Person("John")
Traceback (most recent call last):
File "<pyshell#45>", line 1, in <module>
person = Person("John")
TypeError: Person.__init__() missing 2 required positional arguments: 'age' and 'music'
The same happens with Ruby; this is the class template:
class Person
def initialize(name, age, music)
@name = name
@age = age
@music = music
end
end
person = Person.new("John", 36, ["rock", "punk", "classical"])
Let's recreate the error just passing "John"
.
person = Person.new("John")
(irb):28:in `initialize': wrong number of arguments (given 1, expected 3) (ArgumentError)
from (irb):35:in `new'
from (irb):35:in `<main>'
from /Users/enriquesalceda/.rbenv/versions/3.0.1/lib/ruby/gems/3.0.0/gems/irb-1.3.5/exe/irb:11:in `<top (required)>'
from /Users/enriquesalceda/.rbenv/versions/3.0.1/bin/irb:23:in `load'
from /Users/enriquesalceda/.rbenv/versions/3.0.1/bin/irb:23:in `<main>'
Both languages will allow the creation of default values with a different syntax. However, Golang enables you to create a Struct without specifying the attributes at instantiation.
Let's say you have a similar data structure to the previous example in Golang and we make multiple instantiation combinations:
package main
import "fmt"
type Person struct {
Name string
Age int
Music []string
}
func main() {
personOne := Person{
Name: "Tom",
Age: 20,
Music: []string{"Rock", "Bossa Nova"},
}
personWithoutMusic := Person{
Name: "Jane",
Age: 20,
}
emptyPerson := Person{}
fmt.Printf("%+v\n", personOne)
fmt.Printf("%+v\n", personWithoutMusic)
fmt.Printf("%+v\n", emptyPerson)
}
We created a person with all the required data, another without music and a completely empty person. This didn't raise an error, and we can see the following output.
{Name:Tom Age:20 Music:[Rock Bossa Nova]}
{Name:Jane Age:20 Music:[]}
{Name: Age:0 Music:[]}
As you can see, Go is not raising an error. So, why this is happening? As I said, I would consider Golang a data-oriented programming language and not an object-oriented language. Go is simple and does not implement a presence validation by default; Go lets you define those validations somewhere else if you want in a factory or the design pattern of your choice. Go will set every value to a zero state unless we specify the value for that attribute. Go cares about data integrity. Therefore those values will exist, and this is not free; those attributes having a zero state value have those available bytes in memory, and an empty Person
struct still has the required space for a string, an integer and an array of strings in memory.
So, what if we want a constructor? That is not a difficult task; we can create a Person
constructor as follows.
package main
import "fmt"
type Person struct {
Name string
Age int
Music []string
}
func NewPerson(name string, age int, music []string) *Person {
return &Person{
Name: name,
Age: age,
Music: music,
}
}
func main() {
person := NewPerson("Mick", 78, []string{"rock", "blues", "jazz"})
fmt.Printf("%+v\n", person)
}
Then, without the need of any specific validation the constructor will complain if do not pass any of the expected parameters.
import "fmt"
type Person struct {
Name string
Age int
Music []string
}
func NewPerson(name string, age int, music []string) *Person {
return &Person{
Name: name,
Age: age,
Music: music,
}
}
func main() {
person := NewPerson("Mick")
fmt.Printf("%+v\n", person)
}
This will raise a not enough arguments error
./prog.go:22:22: not enough arguments in call to NewPerson
have (string)
want (string, int, []string)
Go build failed.
Output:
./prog.go:22:22: not enough arguments in call to NewPerson
have (string)
want (string, int, []string)
Go build failed.