Ver en Español
Object Oriented Programming in Go
Apr 16, 2023
Updated: Jun 25, 2026

Object Oriented Programming in Go

Object oriented programming (OOP) is a programming paradigm that focuses on abstracting the components of a program to create a relationship between them. This is achieved using objects, and these objects can interact with each other through messages. These objects have attributes (properties) and behaviors (methods).

The design philosophy of Go

Go's roots are based on C and, more broadly, on the Algol family. Ken Thompson half jokingly said that Rob Pike, Robert Griesemer and himself got together and decided they hated C++. Joke or not, Go is very different from C++. More details on that later. Go is all about maximum simplicity. Rob Pike explains this in detail in "Less is exponentially more".

Go versus other languages

Go has no classes, no objects, no exceptions and no templates. It has garbage collection and built in concurrency. The most notable omission when it comes to object orientation is that Go has no type hierarchy. This contrasts with most object oriented languages such as C++, Java, C#, Scala, and even with dynamic languages like Python and Ruby.

Object oriented features of the Go language

Go has no classes, but it has types. In particular, it has structures (structs). Structures are user defined types. Struct types (with methods) serve purposes similar to classes in other languages.

Structures (structs)

A structure defines state. Later on we'll see a Creature structure. It has a name field (Name) and a boolean flag called Real, which tells us whether it's a real or an imaginary creature. Structures only hold state, not behavior.

In Go, object oriented programming is achieved by using structs to define the state of objects, and methods to manipulate that state. Methods also serve to define behaviors. Methods are defined as functions, but they receive a receiver value that lets them access the object's state. This is useful to organize the code and improve reuse.

Let's look at a first example. Here a Dog anonymously embeds an Animal, so it inherits its fields and methods through composition:

package main

import "fmt"

type Animal struct {
	Name string
	Age  int
}

func (a Animal) Speak() {
	fmt.Println("Hola!")
}

func (a Animal) Walk() {
	fmt.Println("Caminando...")
}

type Dog struct {
	Animal
	Breed string
	Size  int
}

func (d Dog) Bark() {
	fmt.Println("¡Guau!")
}

func (d Dog) Bite() {
	fmt.Println("Mordiendo...")
}

func main() {
	d := Dog{
		Animal: Animal{
			Name: "Fido",
			Age:  5,
		},
		Breed: "Dálmata",
		Size:  10,
	}
	d.Speak()
	d.Walk()
	d.Bark()
	d.Bite()
}

Anonymous embedding

In Go you can embed types within one another. For example, if you embed a structure without naming the field, the embedded structure provides its state (and its methods) directly to the structure that contains it. Let's look at an example with FlyingCreature, which embeds Creature:

type Creature struct {
	Name string
	Real bool
}

type FlyingCreature struct {
	Creature
	WingSpan int
}

dragon := &FlyingCreature{
	Creature{"Dragon", false},
	15,
}

fmt.Println(dragon.Name)
fmt.Println(dragon.Real)
fmt.Println(dragon.WingSpan)

Notice how we access dragon.Name and dragon.Real directly, even though those fields belong to Creature. That's what anonymous embedding gives us.

Interfaces in Go

Interfaces are the hallmark of Go's object oriented support. They are types that declare sets of methods, but have no implementation. Interfaces define the behavior of an object, and any object that implements an interface must implement those behaviors. The interesting part is that in Go the implementation is implicit: you don't declare that a type implements an interface, it's enough that it has the methods. For example, we can define an Animal interface:

type Animal interface {
	MakeNoise() string
	Move() string
}

type Dog struct {
	breed string
	age   int
}

func (d Dog) MakeNoise() string {
	return "Woof!"
}

func (d Dog) Move() string {
	return "Run!"
}

Because Dog has the MakeNoise() and Move() methods, it satisfies the Animal interface automatically.

Encapsulation in Go

Go encapsulates things at the package level. Names that begin with a lowercase letter are only visible within that package. You can hide anything in a package and expose only the types, interfaces and factory functions you want.

Inheritance in Go

Inheritance has always been a controversial topic in object oriented programming. Go has no hierarchy of any kind, but it allows sharing implementation details through composition. Composition by embedding an anonymous type is equivalent to implementation inheritance.

Polymorphism in Go

Polymorphism is the essence of object oriented programming: the ability to treat objects of different types uniformly as long as they adhere to the same interface. Go's interfaces provide this ability in a very direct and intuitive way.

In short, Go is a powerful and flexible programming language that offers a wide range of features for object oriented programming. With its focus on composition, interfaces and anonymous embedding, Go provides a unique and effective experience for those who want to make the most of this paradigm. Have fun exploring Go.

Object oriented design: the Go way

In this section we'll dig deeper into how Go compares with the pillars of object oriented programming: encapsulation, inheritance and polymorphism.

Encapsulation in Go (revisited)

We already discussed how Go encapsulates things at the package level, but let's go deeper with one more example. To hide the foo type and expose only the interface, we declare it in lowercase and provide a NewFoo() function that returns the public Fooer interface:

type Fooer interface {
	Foo1()
	Foo2()
	Foo3()
}

type foo struct {
}

func (f foo) Foo1() {
	fmt.Println("Foo1() here")
}

func (f foo) Foo2() {
	fmt.Println("Foo2() here")
}

func (f foo) Foo3() {
	fmt.Println("Foo3() here")
}

func NewFoo() Fooer {
	return &foo{}
}

Because foo is lowercase, it stays hidden outside the package. Whoever consumes your package will only see the Fooer interface and the NewFoo() function.

Inheritance in Go (revisited)

As we mentioned earlier, Go has no hierarchy of any kind, but it allows sharing implementation details through composition. Composition by embedding an anonymous type is equivalent to implementation inheritance.

Here is an example where SuperFooer embeds the Fooer interface, but does not implement its methods. The Go compiler will let you create a new SuperFooer and call the Fooer methods, but it will obviously fail at runtime, because the embedded interface is nil:

type SuperFooer struct {
	Fooer
}

func main() {
	s := SuperFooer{}
	s.Foo2()
}

Polymorphism in Go (revisited)

Polymorphism in Go is achieved through interfaces. Here is an example where multiple creatures (and a door!) are created that implement the Dumper interface, and then the Dump() method is called for each one:

package main

import "fmt"

type Creature struct {
	Name string
	Real bool
}

func Dump(c *Creature) {
	fmt.Printf("Name: '%s', Real: %t\n", c.Name, c.Real)
}

func (c Creature) Dump() {
	fmt.Printf("Name: '%s', Real: %t\n", c.Name, c.Real)
}

type FlyingCreature struct {
	Creature
	WingSpan int
}

func (fc FlyingCreature) Dump() {
	fmt.Printf("Name: '%s', Real: %t, WingSpan: %d\n",
		fc.Name,
		fc.Real,
		fc.WingSpan)
}

type Unicorn struct {
	Creature
}

type Dragon struct {
	FlyingCreature
}

type Pterodactyl struct {
	FlyingCreature
}

func NewPterodactyl(wingSpan int) *Pterodactyl {
	pet := &Pterodactyl{
		FlyingCreature{
			Creature{"Pterodactyl", true},
			wingSpan,
		},
	}
	return pet
}

type Dumper interface {
	Dump()
}

type Door struct {
	Thickness int
	Color     string
}

func (d Door) Dump() {
	fmt.Printf("Door => Thickness: %d, Color: %s", d.Thickness, d.Color)
}

func main() {
	creature := &Creature{"some creature", false}
	uni := Unicorn{
		Creature{"Unicorn", false},
	}
	pet1 := &Pterodactyl{
		FlyingCreature{
			Creature{"Pterodactyl", true},
			5,
		},
	}
	pet2 := NewPterodactyl(8)
	door := &Door{3, "red"}

	Dump(creature)
	creature.Dump()
	uni.Dump()
	pet1.Dump()
	pet2.Dump()

	creatures := []Creature{
		*creature,
		uni.Creature,
		pet1.Creature,
		pet2.Creature,
	}
	fmt.Println("Dump() through Creature embedded type")
	for _, creature := range creatures {
		creature.Dump()
	}

	dumpers := []Dumper{creature, uni, pet1, pet2, door}
	fmt.Println("Dump() through Dumper interface")
	for _, dumper := range dumpers {
		dumper.Dump()
	}
}

Conclusion

Go is a true object oriented programming language. It allows object based modeling and promotes the best practice of using interfaces instead of concrete type hierarchies. Go made some unusual syntactic decisions, but overall, working with types, methods and interfaces feels simple, lightweight and natural.

Inheritance is one of the main concepts related to object oriented programming, and Go supports it through composition syntax. This means Go types can have one or more fields associated with other types. At the same time, the methods associated with a type can call the methods of the associated types. This allows for a simpler form of inheritance, without needing to define classes.

Embedding is a slightly different form of inheritance. In Go, a type can include other types within its definition. This lets you leverage the behavior of the embedded types, while each type can have its own additional behavior. Embedding is a powerful tool that helps simplify object oriented programming.

Interfaces are one of Go's main features. They let you define a contract for the types that implement them. This provides a standardized way to treat different kinds of objects in the same way. Interfaces present a clear way to define the functionality of objects without needing an explicit type hierarchy.

In short, Go is an object oriented programming language that supports inheritance, embedding and interfaces. Inheritance allows the composition of types and methods, embedding allows behavior reuse and interfaces define a contract for the types that implement them. These features make Go a modern and powerful object oriented programming language.

Suggested exercises

Below I leave you 10 more exercises to practice structures in Go, and to send the solution as a Pull request to this repository: Go Para Principiantes.

  1. Create an interface in Go called Shape with the methods Area() and Perimeter().
  2. Create two types in Go, Rectangle and Square, that implement the Shape interface.
  3. Add some fields, such as width and height, to the Rectangle struct to calculate the area and the perimeter.
  4. Add a field, side, to the Square struct to calculate the area and the perimeter.
  5. Write a method on Rectangle that calculates the area.
  6. Write a method on Rectangle that calculates the perimeter.
  7. Write a method on Square that calculates the area.
  8. Write a method on Square that calculates the perimeter.
  9. Create an instance of each type, Rectangle and Square.
  10. Call the Area() and Perimeter() methods for each instance and verify whether the results are correct.

That's all, I hope this post is useful to you and that you can apply it to a project you have in mind. Leave me a comment if it helped, if you want to add an opinion, or if you have any questions. And remember, if you liked it, you can also share it using the social links below. Good luck exploring Go!

Sebastian Gomez

Sebastian Gomez

Creador de contenido principalmente acerca de tecnología.

Leave a Reply

0 Comments

Advertisements

Related Posts

Categorias