Basic Object-Oriented Programming in Go
Is Go an object-oriented language? The official website offers an answer to this: "Yes and No". Basically Go does not intend to serve as an full object-oriented language like Ruby, but you can use it only in that way, like we do it in JavaScript. In fact, Go does not have the term "object", but there are other approaches to help us to do so.
Struct & Methods
In Ruby, we use class
and def
keywords to create a class and its methods like:
class Post
def title
"Title"
end
end
In Go, we can do the same with methods
:
type Post struct {
}
func (p *Post) Title() string {
return "Title"
}
func main() {
p := Post{}
fmt.Println(p.Title())
}
As we declare the Post
struct, we continue to declare the Title()
function and designate it as a method
of the type Post
, so we can retrieve the title using p.Title()
in the main function.
This data structure allows us to separate files like we do in a object-oriented language, like in Ruby we name a file post.rb
as it contains solely the Post
class.
Custom Type
Like in Ruby, we can create custom types from existing types:
type Num int
func (n *Num) Zero() int {
return 0
}
func main() {
var g Num = 7
fmt.Println(g + g) // => 14
fmt.Println(g.Zero()) // => 0
}
The Num
type includes all features of int
type, and allows to add custom functions on it.
However, you may start wonder, we can create several types and make them inherit each other, so that is actually type inheritance.
Type Inheritance?
Nevertheless, the offical claims that there is no type inheritance in Go, which means the following code is broken:
type Num int
type Numm Num
func (n *Num) Zero() int {
return 0
}
func main() {
var g Num = 7
var h Numm = 8
fmt.Println(g + g) // => 14
fmt.Println(h + h) // => 16
fmt.Println(h.Zero()) // => h.Zero undefined (type Numm has no field or method Zero)
}
The Numm
type delegates the functions from int
type, but does not delegates the Zero()
method from Num
type, which is an intentional design in Go. The purpose is to avoid the confusion in complicated delegations in a type-safe programming environment. There are still other ways to achieve so.
Type Embedding
One way to achieve type inheritance is to use type embedding. It is more like in Rails' ActiveRecord that we call another model using relatinoships:
class Post < ActiveRecord::Base
belongs_to :author
end
class Author < ActiveRecord::Base
has_many :posts
end
Post.first.author.name #=> "John"
Easy to understand. In Go, we can define this relationship within the struct
:
type Post struct {
Author Author
}
type Author struct {
Name string
}
func main() {
author := Author{"John"}
post := Post{author}
fmt.Println(post.Author.Name // => "John"
}
When declaring a new type, we can implement another type into its fields. In this way, retrieving the author's name with post.Author.Name
is more instictive and understandable with this object-oriented idea. Futhermore, we can use "Anonymous Field" to get closer to type inheritance.
Anonymous Field
The way anonymous field works is to embed a specific type into a field but does not give it a name. In this approach, the new type will have the methods you have defined for another type:
type Post struct {
Title string
}
func (post *Post) Author() {
fmt.Println("John")
}
type Article struct {
Post
}g
func main() {
post := Post{"Golang"}
post.Title // => "Golang"
post.Author() // => "John"
artirle := Article{}
article.Title // => ""
article.Author() // => "John"
another_artirle := Article{Post{"Golang"}}
another_article.Title // => "Golang"
}
Now the article
has the Atuhor()
method defined in Post
type. This looks great! Except that it is only a way to get the methods but not actually type inheritance. Unlike Ruby in which we can do String.new.is_a?(Object)
and get true
, if you check the type of the article
, it has nothing to do with type Post
. They are still separate types.
Also, this technique may introduce confusion if the same method is defined in two structs like:
type Post struct {
Title string
}
func (post *Post) Author() {
fmt.Println("John")
}
type Article struct {
Post
}
func (post *Post) Author() {
fmt.Println("Martin")
}
func main() {
article := Article{}
article.Author() // => "Martin"
}
The Author()
is defined both in Post
and Article
structs, so the atricle.Author()
reads the method from the latter first. This is a typical confusion we see in other OO language if we are being careless designing the application.