liveBook · Manning

Encapsulation

As explained in 100 Go Mistakes and How to Avoid Them

Encapsulation is a software design technique that restricts direct access to state by placing it behind controlled operations or boundaries.

Encapsulation hides state behind controlled operations or boundaries. A common mistake is treating a package-level variable as convenient shared state: any package function can alter it, and tests lose isolation. section 2.3.2

var db *sql.DB
 
func init() {
    dataSourceName :=
        os.Getenv("MYSQL_DATA_SOURCE_NAME")       #1
    d, err := sql.Open("mysql", dataSourceName)
    if err != nil {
        log.Panic(err)
    }
    err = d.Ping()
    if err != nil {
        log.Panic(err)
    }
    db = d                                        #2
}

This initialization exposes a database connection through the global db variable. init runs before tests, creates an external dependency automatically, and leaves error handling inside initialization. section 2.3.2

func createClient(dsn string) (*sql.DB, error) {    #1
    db, err := sql.Open("mysql", dsn)
    if err != nil {
        return nil, err                             #2
    }
    if err = db.Ping(); err != nil {
        return nil, err
    }
    return db, nil
}

A plain function keeps the connection local, returns errors to its caller, and allows integration tests to invoke setup explicitly. section 2.3.2

Getters and setters can encapsulate behavior, not merely fields. They can validate values, return computed results, protect access with a mutex, hide representation, or provide a debugging interception point. They are not automatically idiomatic in Go. section 2.4

timer := time.NewTimer(time.Second)
<-timer.C                             #1

Direct field access can be appropriate: the standard library exposes timer.C for receiving events rather than requiring a getter. section 2.4

currentBalance := customer.Balance()     #1
if currentBalance < 0 {
    customer.SetBalance(0)               #2
}

Use methods when access needs a policy or behavior: Balance reads the value, while SetBalance can enforce a nonnegative balance. section 2.4

Choose the boundary according to the behavior it must protect:

  • Replace global initialization with an explicit function when callers should handle errors, choose setup timing, or isolate tests. section 2.3.2
  • Keep init when setup cannot fail, needs no global state, and does not affect unit tests, such as static HTTP handler registration. section 2.3.2
  • Prefer direct fields when methods add no value; use Balance and SetBalance when validation, representation flexibility, synchronization, or debugging justifies them. section 2.4

How 100 Go Mistakes and How to Avoid Them covers it

How deep it goes
Explains global-state consequences and getter/setter trade-offs with Go examples, stopping short of a general object-encapsulation framework.
Examples
Uses database initialization, static HTTP registration, time.Timer field access, and customer balance methods.
What you can do after
Diagnose when global state, init functions, direct fields, or accessors undermine testability, error handling, or useful behavior.
How it connects
Connects encapsulation with global variables, init functions, unit testing, error handling, getters, setters, and mutexes.

Also explained in

Compare how 14 books explain encapsulation →

Related concepts

This explainer was generated by Manning's knowledge graph from the text of 100 Go Mistakes and How to Avoid Them. The section links point to the parts of the book it draws on.