Is Go an Object Oriented language?

The first technical GophersLand article is dedicated to a slightly opinionated topic but an important one. Soon after you write your first Go program, you will start thinking about how to organise your code. Should I write a Function? Should I create a new Struct?

Eventually it all comes to the same question.

Is Go an object oriented language?

Yes and no. Although Go has types and methods and allows an object-oriented style of programming, there is no type hierarchy. The concept of “interface” in Go provides a different approach that we believe is easy to use and in some ways more general. There are also ways to embed types in other types to provide something analogous — but not identical — to subclassing. Moreover, methods in Go are more general than in C++ or Java: they can be defined for any sort of data, even built-in types such as plain, “unboxed” integers. They are not restricted to structs (classes).

Also, the lack of a type hierarchy makes “objects” in Go feel much more lightweight than in languages such as C++ or Java.

Source: https://golang.org/doc/faq#Is_Go_an_object-oriented_language

The answer is, like to everything in life: “it depends”.

Today’s article is presented by GophersLand citizen: Lukas [twitter].

About Author

I was doing PHP/Java for 7 years by now. I developed a bidding applications handling 100s of millions of bids from advertisers such as Booking.com or Expedia. I created a poker social network TiltBook and even published a Udemy course on “Object Oriented Programming”!

About OOP

Before we are able to decide if Go is OO or not, we must first define an OO language.

When I started writing this article, I defined OOP based on the Wikipedia definition:

  • is a programming paradigm based on the concept of “objects”, which may contain data, in the form of fields, often known as attributes; and code, in the form of procedures, often known as methods
  • an Object’s procedures can access and often modify the attributes of the object with which they are associated
  • an Object’s internal state is protected from outside world (encapsulated) leveraging private/protected/public visibility of attributes and methods
  • an Object is frequently defined in OO languages as an instance of a Class

The above concept properties are implemented in most popular OO languages, Java and C++ by mechanics such as:

  • Encapsulation (possible on package level in Go)
  • Composition (possible through embedding in Go)
  • Polymorphism (possible through Interface satisfaction in Go. Type satisfies Interface without manually implementing it if it defines all the Interface methods. Since almost anything can have methods attached, even primitive types such as Int, almost anything can satisfy an interface)
  • Inheritance (Go does not provide the typical, type-driven notion of subclassing because it’s fragile and considered a bad practice, inferior to Composition)

OOP Original Conception

After I published the article, I realised the concept of Objects from historical perspective is extremely complex and subjective.

What surprised me the most is the fact, the creator of the term “object oriented”, Dr Alan Kay didn’t based the methodology on previously mentioned mechanics (Encapsulation, Composition, Polymorphism and Inheritance), those evolved further as side effects.

Alan Kay’s original conception was based on the following properties (thx Michael Kohl for providing this resource):

  1. Messaging (possible via Channels in Go)
    In terms of communication between Objects, how modules, objects communicate should be designed rather than what their internal properties and behaviors should be
  2. Local retention, protection, and hiding of state-process (possible by defining public/private attributes and methods in Go)
  3. Extreme late-binding of all things (possible via higher-order-functions and Interfaces in Go)
    A higher order function is a function that takes a function as an argument, or returns a function.
- I thought of objects being like biological cells and/or individual 

computers on a network, only able to communicate with messages (so

messaging came at the very beginning -- it took a while to see how
to do messaging in a programming language efficiently enough to be 

useful).


- I wanted to get rid of data. The B5000 almost did this via its

almost unbelievable HW architecture. I realized that the

cell/whole-computer metaphor would get rid of data, and that "<-"

would be just another message token (it took me quite a while to

think this out because I really thought of all these symbols as
names for functions and procedures.


- My math background made me realize that each object could have

several algebras associated with it, and there could be families of

these, and that these would be very very useful. The term

"polymorphism" was imposed much later (I think by Peter Wegner) and

it isn't quite valid, since it really comes from the nomenclature of

functions, and I wanted quite a bit more than functions. I made up a

term "genericity" for dealing with generic behaviors in a

quasi-algebraic form.

Additional deep sources on OOP:

Local retention, protection, and hiding of state-process

In other words, Encapsulation

is an object-oriented programming concept that binds together the data and functions that manipulate the data, and that keeps both safe from outside interference and misuse.

Yet, everybody uses setters (a guaranteed way to ruin your state) and gets OOP wrong right at the beginning but that’s a topic for another discussion.

Encapsulation is all about maintaining your objects in a valid state and data hiding. Majority of OOP codebases are monoliths. Well, at least until 2017 before the whole Microservices boom…

Why replacing set of problems for less researched ones?

Monoliths apps have usually one big state, shared memory and state access is controlled using famous private/protected/public attributes/methods.

You know what? That works very well for a wide range of applications if you follow DDD and achieve a proper encapsulation!

OOP strengths: Individualism, Encapsulation, Shared memory, Mutable State

How is encapsulation and shared memory achieved in object oriented languages?

public class FirstGopherlandCodeSnippet {
private boolean excitementLvl = 99;
}

What makes GoLang special?

The rise of multicore CPUs argued that a language should provide first-class support for some sort of concurrency or parallelism. And to make resource management tractable in a large concurrent program, garbage collection, or at least some sort of safe automatic memory management was required.

All this is very hard to achieve in current Object Oriented languages such as Java. Go took a different approach.

Do not communicate by sharing memory; instead, share memory by communicating

The number 1 feature of GoLang is the exact opposite definition of what OOP stands for. The alone definition, should be a sufficient first hint, suggesting a different implementation/way of thinking must be adapted.

This efficient communication is achieved especially using:

What about encapsulation? How is encapsulation achieved in GoLang?

Encapsulation

in Go is done on a package level by exporting Structs and Functions by capitalizing their first character.

package tar
// Private, available only from within the tar package
type header struct {
}
// Public, available from other packages
func Export() {
}
// Private, available only from within the tar package
func doExport(h header) {
}

I like to think about it in this way:

A GoLang package is the equivalent of a typical*Manager/*Handler Class in Java.
ArchiveManager, TarManager, ZipManager…

Therefore, when designing your package specific scope encapsulation, start by designing a public API by defyining the minimum set of FUNCTIONS that shall be exposed to the outsite world.

An usage example of a standard library RSA pkg with clear input/output API:

package rsa
// EncryptPKCS1v15 encrypts the given message with RSA and the padding scheme from PKCS#1 v1.5.
func EncryptPKCS1v15(rand io.Reader, pub *PublicKey, msg []byte) ([]byte, error) {
return []byte("secret text"), nil
}
cipherText, err := rsa.EncryptPKCS1v15(rand, pub, keyBlock)

There is no need to jump into creating a Struct straight away just because we are used to create Classes such as RsaEncryptionManager/RsaHandler for everything all the time from previous, differently designed languages.

KISS.

Keep it simple stupid.

Keep it a function.

Struct vs Object

The responsibility of an Object is to:

  • hide data from outsite world
  • define behaviour
  • perform messaging
  • maintain a valid state

The responsibility of a Struct is to:

  • maintain a valid state if implemented via pointers
  • group together multiple fields, even of a different type

Go’s structs are typed collections of fields. They’re useful for grouping data together to form records.

Ability to directly attach methods should be done to specific, small Structs, before things get messy. Just because there isn’t yet a term “GodStruct” (well, now there is), it doesn’t mean a Struct is excused to look like this monstrosity:

… I still love you Ethereum but good luck unit testing that thing…

I think the biggest “illusion” why Go looks like an Object Oriented language is due to the fact a Struct and an Object look so freaking simillar therefore is natural for developers coming from OO languages to write Go in such a manner. Except. They are not.

How does a Struct look like?

type MyFakeClass struct {
attribute1 string
}

Can such a Class (Struct) also have methods? Sure.

func (mc MyFakeClass) printMyAttribute() {
fmt.Println(mc.attribute1)
}

Perfect. Except… We have been both fooled like Instagram fanboys/fangirls by filters and convenient angles of that girl/guy from next doors…

The above method with a receiver argument (the Go term), can be also written as a regular function.

func printMyAttribute(mc MyFakeClass) {
fmt.Println(mc.attribute1)
}
This is the version Go compiler actually uses behind the scenes!

Thank you Mihalis Tsoukalos, author of a book Mastering Go for this epiphany!

Imagine you would be writing all your code in this manner, would you still create a GodStruct and pass it around by value all the time?

Struct vs Object behaviour

The difference is noticable from the following example.

Click to run the snippet: https://play.golang.org/p/FQa09CzdtRh

We could achieve the desired “Object Oriented” behaviour by implementing a common pointer receiver function.

Click to run the snippet: https://play.golang.org/p/lNk3u1np5VM

While this is a common pattern (technically, the only possible way to update a Struct without creating a new instance) and a recommended way of updating a Struct, I believe pointers should be used with a caution. But then, I am a huge fun of immutability so I may be slightly bias.

In the next section I am going to share my experience and some researched points when to use a Value Receiver and when a Pointer Receiver.

Value vs Pointer Receiver

When to use a Value Receiver:

  • if the receiver is a map, func or chan
  • if the receiver is a slice and the method doesn’t reslice or reallocate the slice
  • if the receiver doesn’t mutate its state
  • if the receiver is a small/medium array or struct that is naturally a value type (for instance, something like the time.Time type, XY coordinates, basically anything representing collection of fields), a ValueObject one could say

Advantages of a Value Receiver:

  • concurrently safe, compatible with GoRoutines (that’s why I use Go in first place)
  • Value Copy Cost associated with Value Receivers is not a performance bottleneck issue in absolute majority of applications
  • can actually reduce the amount of garbage that can be generated; if a value is passed to a value method, an on-stack copy can be used instead of allocating on the heap. The stack is faster because the access pattern makes it trivial to allocate and deallocate memory from it (a pointer/integer is simply incremented or decremented), while the heap has much more complex bookkeeping involved in an allocation or free
  • it directly, conciously forces to design small Structs
  • easier to argue about encapsulation, responsibilities
  • keep it simple stupid. Yes, pointers can be tricky because you never know the next project’s dev and Go surely is a young language
  • obvious I/O
  • unit testing is like walking through pink garden (slovak only expression?), means easy
  • no NIL if conditions (a NIL can be passed to a pointer receiver and cause a panic attack)

Advantages of a Pointer Receiver:

  • more efficient once allocated in terms of CPU and Memory (are those few ms actually a business requirement?)
  • can maintain and mutate state

When to use a Pointer Receiver:

  • working with large datasets, Pointer Receiver is more efficient
  • if the developer writes high performance analytical application like NewRelic or a new Blockchain DataStore DB
  • if the receiver is a Struct that contains a sync.Mutex or similar synchronizing field, the receiver must be a pointer to avoid copying (Why mutex in first place? Use channels)
  • if the receiver performs mutation (can be mostly avoided by designing pure functions with clear intention and obvious I/O)
  • if a Struct is maintaining state, e.g. TokenCache
type TokenCache struct {
cache map[string]map[string]bool
}
func (c *TokenCache) Add(contract string, token string, authorized bool) {
tokens := c.cache[contract]
if tokens == nil {
tokens = make(map[string]bool)
}
tokens[token] = authorized
c.cache[contract] = tokens
}

My personal rules when designing a Struct to maintain its state:

  • I make sure ALL attributes are PRIVATE, interaction is possible only via defined method receivers
  • I don’t pass this Struct to any goroutine

General rule for both types

If your Struct has a Value Receiver method, all other methods of this Struct should be Value Receivers.

The same for pointers.

If your Struct has a Pointer Receiver method, all other methods of this Struct should be Pointer Receivers.

Consistency FTW!

Personal ultimate 2 rules:

  • Write as many First Class Functions as possible and KISS.

Programmers new to Go are often surprised by its support for function types, functions as values, and closures. The First Class Functions in Go code walk demonstrates these features with a simulation of the dice game Pig. It is a pretty program that uses the language to great effect, and a fun read for Go beginners and veterans alike.

func roll(s score) (score, bool) {
outcome := rand.Intn(6) + 1 // A random int in [1, 6]
if outcome == 1 {
return score{s.opponent, s.player, 0}, true
}
return score{s.player, s.opponent, outcome + s.thisTurn}, false
}
  • Abstract business complexity into separate functions, even tight 1 function interfaces such as famous standard library io.Reader, io.Writer and comfortably pass them as function arguments, unit testing and mocking will become extremely convenient (more on this topic in incoming blog post)
type Reader interface {
Read(p []byte) (n int, err error)
}
type Writer interface {
Write(p []byte) (n int, err error)
}
func JSON(reader io.Reader) (ABI, error) {

Extra resources on topic Value vs Pointer vs Function in Go:

Summary

Go was not designed to be primarely Object Oriented language in the way languages such as Java/PHP/C++ were but the comparison is necessary because those are the languages majority of developers are coming from as fresh Gophers, bringing old solutions, techniques with them that may not necessarily be good solutions in Go environment.

Go is a combination of paradigms.

  • It satisfies majority of Object Oriented characteristics (both mainstream and original ones, as stated at the beginning of the article) to solve general software extendibility issues and provide a way for developers to design domains more naturally
  • It masters Functional/Procedural/Objects characteristics and Messaging to solve problems with concurrency and parallelism in the era of multicore CPU hardware architecture

Did you enjoy this article?

Join GophersLand

A kingdom where Gophers of all kind live in peace, share, educate and develop software. If you, perhaps, feel like one of us, crawing the Go knowledge, choose your character and join our majestic community!

https://gophersland.com

See you in the libraries of GophersLand’s kingdom my friend,

All the best,

https://gophersland.com community

PS: Took me ages to write all the research down. 50 claps never killed any Gopher! I guess…so clap, tweet, share, spread! :)

Like what you read? Give Lukas Lukac a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.