Understanding Encapsulation in Go

Go, although not a traditional Object-Oriented Programming (OOP) language, provides encapsulation through the use of structs and package-level visibility. Encapsulation in Go is about controlling access to data and methods within packages, enabling you to create robust and maintainable applications.

To illustrate, consider a Go struct representing a bank account. Without encapsulation, the account balance could be directly altered. With encapsulation, however, the balance can only change through specific methods, like depositing or withdrawing.

package main

import (
    "fmt"
)

// BankAccount struct
type BankAccount struct {
    balance float64 // not using encapsulation
}

// Withdraw method to withdraw money
func (acc *BankAccount) Withdraw(amount float64) {
    acc.balance -= amount
}

// Deposit method to deposit money
func (acc *BankAccount) Deposit(amount float64) {
    acc.balance += amount
}

func main() {
    account := &bank.BankAccount{}
    account.balance += 1000 // directly accessing the balance
}
Encapsulation: Managing Data Privacy with Visibility

In Go, data privacy is managed through the visibility of identifiers. By convention, identifiers starting with a lowercase letter are unexported and accessible only within the same package. In contrast, identifiers that begin with an uppercase letter are exported and accessible from other packages.

For example, let's consider a Go struct named Person, which includes an unexported field name.

person/person.go

package person

// Person struct with an unexported field
type Person struct {
    name string
}

// NewPerson is a constructor function
func NewPerson(name string) *Person {
    return &Person{name: name}
}

main.go

package main

import (
    "fmt"
    "codesignal/person"
)

func main() {
    person := person.NewPerson("Alice")
    // The following line causes an error due to unexported access:
    fmt.Println(person.name)
}
Exported Methods for Controlled Access

In Go, encapsulation utilizes exported methods on structs to access or modify the unexported fields. Let's illustrate this through a simple example.

dog/dog.go

package dog

// Dog struct with an unexported attribute
type Dog struct {
    name string
}

// NewDog is a constructor function
func NewDog(name string) *Dog {
    return &Dog{name: name}
}

// SetName is an exported method to modify the name
func (d *Dog) SetName(name string) {
    d.name = name
}

// Name is an exported method to retrieve the name
func (d *Dog) Name() string {
    return d.name
}

main.go

package main

import (
    "fmt"
    "codesignal/dog"
)

func main() {
    myDog := dog.NewDog("Max")
    myDog.SetName("Buddy")
    fmt.Println(myDog.Name()) // Output: Buddy
}
Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal