Object Oriented Go
September 7, 2024 3:08 PM
Go is quickly becoming one of my favourite languages to work with. It's simple, concise and has a lot of power behind it. I've been using it for a while now and I've been learning a lot about the language and its features. However, one thing that I've been struggling with is how to write object-oriented code in Go. Go is a statically typed language and it doesn't have classes like other object-oriented languages such as Ruby, which for me as a Ruby developer, has been a bit of a challenge. But after some time and a lot of reading, I've come to understand how to write object-oriented code in Go. In this post, I'll share some of the things I've learned about writing object-oriented Go.
Structs
In Go, you can define a struct to represent a data structure. A struct is a collection of fields that can be of different types. You can define a struct like this:
type Person struct {
Name string
Age int
}
Here we define a struct Person
with two fields Name
and Age
. You can create an instance of this struct like this:
p := Person{Name: "John", Age: 30}
You can access the fields of the struct like this:
fmt.Println(p.Name) // John
fmt.Println(p.Age) // 30
Methods
In Go, you can also define methods on a struct. You can think of a method as a function that belongs to a struct. You can define a method on a struct like this:
func (p Person) SayHello() {
fmt.Println("Hello, my name is", p.Name)
}
Here we define a method SayHello
on the Person
struct. You can call the method like this:
p.SayHello() // Hello, my name is John
Behind the scenes, Go is passing the instance of the struct as the first argument to the method. This is similar to how Ruby passes self
as the first argument to an instance method. What this means is that you can access the fields of the struct inside the method using the instance of the struct. You can also modify the fields of the struct inside the method using a pointer to the struct. Here's an example:
func (p *Person) SetName(name string) {
p.Name = name
}
You can call the method like this:
p.SetName("Jane")
fmt.Println(p.Name) // Jane
But what about inheritance?
Go doesn't have inheritance like in Ruby. Instead, Go has composition. You can embed a struct within another struct to achieve composition. Here's an example:
type Employee struct {
Person
Salary int
}
Here we define a struct Employee
that embeds the Person
struct. This means that an Employee
has all the fields and methods of a Person
. You can create an instance of an Employee
like this:
e := Employee{Person: Person{Name: "Jane", Age: 25}, Salary: 50000}
You can also create an instance of an Employee
like this:
p := Person{Name: "Jane", Age: 25}
e := Employee{Person: p, Salary: 50000}
You can then access the fields and methods of the embedded struct like this:
fmt.Println(e.Name) // Jane
fmt.Println(e.Age) // 25
e.SayHello() // Hello, my name is Jane
I long for the Ruby way
One of my favourite things about Ruby is the ability to call methods on objects and pass messages between objects. This is one of the things that I miss the most when working with Go. There are multiple ways of achieving this in Go, one of them is using the actor pattern. I've written a post about it here (if it's not published yet, it will be soon).
Lets say you want to do some arithmetic on some values. Directly you could do something like this:
func add(x, y int) int {
return x + y
}
func main() {
res := add(2, 5)
fmt.Println(res) // 7
}
This works fine but what if you want to do it the Ruby way? The ruby way being to send a message to an object and have it do the work for you. In Ruby you could do something like this:
class Adder
def add(x, y)
x + y
end
end
adder = Adder.new
res = adder.add(2, 5)
puts res # 7
In Go, you can achieve something similar. Here's an example:
package arithmetic
type Calculator struct {
a, b float64
}
func New(a, b float64) *Calculator {
return &Calculator{a: a, b: b, vals: vals}
}
func (c *Calculator) Add() float64 {
return c.a + c.b
}
Here we define a struct Calculator
with two fields a
and b
. We also define a method on the Calculator
struct to do some addition. You can create an instance of a Calculator
like this:
c := arithmetic.New(2, 5)
You can call the method on the Calculator
like this:
fmt.Println(c.Add()) // 7
You can also call the method on the Calculator
like this:
fmt.Println(arithmetic.New(2, 5).Add()) // 7
This approach is pretty rigid but it's a good starting point and allows you to do some basic object-oriented programming in Go. You can also use interfaces to define a contract that a struct must implement, this way you can define a set of methods that a struct must have.
Conclusion
Having been spoilt by Ruby's object-oriented features, I have a deep and fond appreciation for it and I'm always looking for ways to replicate that in other languages. Learning how to do so in Go has been a fun and interesting journey. I hope this post has been helpful to you and has given you some insight into how to write object-oriented code in Go. Please note that I am still learning and there may be better ways of doing things and I'm always open to learning new things, at this time this is how I've been doing things and it's been working for me. If you have any suggestions or feedback, please feel free to reach out and let me know. I'm always looking to learn and improve. Thanks for reading!