Information Hiding in Go
Part of the Object Oriented Programming section of Coddy's GO journey — lesson 50 of 107.
Information hiding goes beyond just making fields unexported. It's about designing your types so that external code only knows what it needs to know—nothing more.
The goal is to expose behavior through methods while keeping the internal structure completely hidden. Consider this approach:
type Counter struct {
value int
maxLimit int
}
func NewCounter(limit int) *Counter {
return &Counter{maxLimit: limit}
}
func (c *Counter) Increment() bool {
if c.value >= c.maxLimit {
return false
}
c.value++
return true
}
func (c *Counter) Value() int {
return c.value
}
External code doesn't know that Counter uses an int internally. It could be a float64, a slice, or something else entirely. The implementation can change without affecting any code that uses Counter—as long as the methods behave the same way.
This principle also applies to helper functions. Keep internal logic unexported:
// Exported - part of public API
func (c *Counter) Reset() {
c.value = 0
}
// unexported - internal helper
func (c *Counter) isAtLimit() bool {
return c.value >= c.maxLimit
}
By hiding isAtLimit(), you're free to change or remove it later. Information hiding creates a clear boundary between what your package promises to external users and how it works internally.
Challenge
EasyLet's build a secure wallet system that demonstrates information hiding. You'll create a digital wallet where the internal balance tracking and transaction history are completely hidden from external code—only behavior is exposed through methods.
You'll organize your code across two files:
wallet.go: Create aWalletstruct that hides all its internal state. The wallet should track the current balance and a spending limit internally, but external code should never know how these are stored or represented. Expose behavior through these methods:NewWallet(limit float64) *Wallet- constructor that creates a wallet with the given spending limit and zero balanceDeposit(amount float64) bool- adds money to the wallet, returnstrueif successful (amount must be positive)Spend(amount float64) bool- removes money if sufficient balance exists AND the amount doesn't exceed the spending limit, returnstrueif successfulBalance() float64- returns the current balanceStatus() string- returns a status message in the format:Balance: $[balance] (Limit: $[limit])
main.go: Read wallet configuration and transactions from input, create a wallet, perform operations, and display results after each action.
The following inputs will be provided:
- Line 1: Spending limit
- Line 2: Deposit amount
- Line 3: First spend amount
- Line 4: Second spend amount
After creating the wallet, print its initial status. Then perform each operation and print either the updated status (if successful) or Transaction failed (if the operation was rejected). Format all dollar amounts with two decimal places.
For example, given 50, 100, 30, and 80, your output should be:
Balance: $0.00 (Limit: $50.00)
Balance: $100.00 (Limit: $50.00)
Balance: $70.00 (Limit: $50.00)
Transaction failedThe last transaction fails because $80 exceeds the $50 spending limit—even though there's enough balance. The wallet enforces its rules internally without exposing how it makes these decisions. External code simply calls methods and receives results, never knowing the internal implementation details.
Cheat sheet
Information hiding means designing types so external code only knows what it needs to know. Expose behavior through methods while keeping internal structure completely hidden.
Example of a type with hidden implementation:
type Counter struct {
value int
maxLimit int
}
func NewCounter(limit int) *Counter {
return &Counter{maxLimit: limit}
}
func (c *Counter) Increment() bool {
if c.value >= c.maxLimit {
return false
}
c.value++
return true
}
func (c *Counter) Value() int {
return c.value
}
External code doesn't know the internal representation (could be int, float64, or anything else). The implementation can change without affecting code that uses it.
Keep helper functions unexported:
// Exported - part of public API
func (c *Counter) Reset() {
c.value = 0
}
// unexported - internal helper
func (c *Counter) isAtLimit() bool {
return c.value >= c.maxLimit
}
Information hiding creates a clear boundary between what your package promises externally and how it works internally.
Try it yourself
package main
import (
"fmt"
)
func main() {
// Read inputs
var limit float64
var depositAmount float64
var spend1 float64
var spend2 float64
fmt.Scanln(&limit)
fmt.Scanln(&depositAmount)
fmt.Scanln(&spend1)
fmt.Scanln(&spend2)
// TODO: Create a new wallet with the given spending limit
// TODO: Print initial status
// TODO: Perform deposit and print result (status or "Transaction failed")
// TODO: Perform first spend and print result
// TODO: Perform second spend and print result
}
This lesson includes a short quiz. Start the lesson to answer it and track your progress.
All lessons in Object Oriented Programming
1Fundamentals of Go OOP
External FilesGo Workspace & ModulesPackages & ImportsExported vs Unexported NamesIntroduction to OOP in GoStructs as ClassesDefining Methods on StructsPointer vs Value ReceiversStruct InitializationConstructor FunctionsRecap - Simple Calculator4Interfaces
Introduction to InterfacesImplicit ImplementationInterface as ContractEmpty Interface (any)Type AssertionType SwitchInterface CompositionStringer & Error InterfacesRecap - Shape Calculator7Encapsulation
Exported vs Unexported FieldsPackage-Level EncapsulationGetter & Setter MethodsInformation Hiding in GoRecap - Student Records10Generics (Go 1.18+)
Introduction to GenericsType ParametersType ConstraintsGeneric StructsGeneric Methods WorkaroundRecap - Generic Collection2Types & Structs Deep Dive
Basic & Composite TypesCustom Type DefinitionsStruct TagsAnonymous StructsNested StructsZero Values & DefaultsRecap - Contact Book5Composition Over Inheritance
Why Go Has No InheritanceStruct Embedding BasicsMethod PromotionEmbedding Multiple StructsEmbedding vs AggregationShadowing Embedded MethodsRecap - Employee Hierarchy8Error Handling & OOP
The error InterfaceCustom Error TypesError Wrapping (fmt.Errorf)Sentinel Errorserrors.Is() and errors.As()Panic, Defer, and RecoverRecap - File Parser3Pointers & Memory
Pointer Basics in GoPointers to StructsPass by Value vs ReferenceThe new() FunctionGarbage Collection in GoRecap - Linked List Builder6Polymorphism in Go
Polymorphism via InterfacesDuck Typing in GoInterface Satisfaction RulesPolymorphic CollectionsDependency InjectionRecap - Payment Processor9Concurrency & OOP
Goroutines BasicsChannels & CommunicationBuffered vs Unbuffered ChanSelect Statementsync.Mutex & sync.RWMutexsync.WaitGroupThread-Safe Struct DesignRecap - Worker Pool