Hello! In this lesson, we're diving into Encapsulation and access control in Go. Unlike some languages, Go manages encapsulation through capitalization conventions. Imagine encapsulation as an invisible barrier that protects the internals of your code. In Go, identifiers (like struct fields and methods) are kept safe using naming conventions: uppercase indicates exported (public), while lowercase indicates unexported (private). This is crucial for creating robust and secure applications!
In Go, encapsulation is achieved through structs and methods. Structs bundle together data, while methods attach functionality. Please note that in Go, the distinction between exported (public) and unexported (private) identifiers in Go is meaningful only across package boundaries, so all code snippets we'll discuss today will span multiple files. As example, imagine to be coding a multiplayer game; you could create a Player
struct, encapsulating fields (health
, armor
, stamina
) and methods (ReceiveDamage
, ShieldHit
, RestoreHealth
).
File content for player/player.go
:
Go1package player 2 3type Player struct { 4 health int 5 armor int 6 stamina int 7} 8 9// Constructor function for creating new Player 10func NewPlayer(health, armor, stamina int) *Player { 11 return &Player{health: health, armor: armor, stamina: stamina} 12} 13 14// Public method to receive damage 15func (p *Player) ReceiveDamage(damage int) { 16 p.health -= damage // Reduce health 17} 18 19// Public method to decrease armor 20func (p *Player) ShieldHit(armorDecrease int) { 21 p.armor -= armorDecrease // Decrease armor 22} 23 24// Public method to restore health 25func (p *Player) RestoreHealth(healthIncrease int) { 26 p.health += healthIncrease // Restore health 27}
File content for main.go
:
Go1package main 2 3import ( 4 "fmt" 5 "player" 6) 7 8func main() { 9 player := player.NewPlayer(100, 50, 77) 10 player.ReceiveDamage(10) 11 player.ShieldHit(5) 12 player.RestoreHealth(15) 13 fmt.Println("Player's Current Health:", player) // Output: Player's Current Health: &{105 45 77} 14}
Here, player
is an instance of the Player
struct with methods you can call to manipulate its state.
In Go, access control is managed through capitalization. An identifier, such as a field or method, is exported (similar to "public") if it begins with an uppercase letter. Conversely, if it starts with a lowercase letter, it remains unexported (similar to "private"). Note that this access control is enforced only across package boundaries.
File content for privacy/privacy.go
:
Go1package privacy 2 3type PrivateExample struct { 4 PublicAttribute string 5 privateAttribute string 6} 7 8// Constructor-like function for creating a new PrivateExample with privateAttribute 9func NewPrivateExample(publicAttr, privateAttr string) *PrivateExample { 10 return &PrivateExample{PublicAttribute: publicAttr, privateAttribute: privateAttr} 11} 12 13// Method that prints attributes (for demonstration purposes) 14func (pe *PrivateExample) PrintAttributes() { 15 fmt.Println("Public Attribute:", pe.PublicAttribute) 16 fmt.Println("Private Attribute:", pe.privateAttribute) 17}
File content for main.go
:
Go1package main 2 3import ( 4 "fmt" 5 "example" 6) 7 8func main() { 9 example := privacy.NewPrivateExample("Public", "Private") 10 example.PrintAttributes() 11 fmt.Println(example.PublicAttribute) // Output: Public 12 // Uncommenting the following line would cause an error because privateAttribute is unexported 13 // fmt.Println(example.privateAttribute) 14}
Private-like access to struct fields in Go is controlled by making struct fields unexported, limiting outside interference and ensuring data integrity. Let's consider a BankAccount
use case and let's see how we can implement this.
File content for bankaccount/bankaccount.go
:
Go1package bankaccount 2 3type BankAccount struct { 4 accountNumber int 5 balance float64 6} 7 8// Constructor function for creating new BankAccount 9func NewBankAccount(accountNumber int, balance float64) *BankAccount { 10 return &BankAccount{accountNumber: accountNumber, balance: balance} 11} 12 13// Public method to deposit money 14func (ba *BankAccount) Deposit(amount float64) { 15 ba.balance += amount // Deposit money 16} 17 18// Method to print balance (for demonstration purposes) 19func (ba *BankAccount) PrintBalance() { 20 fmt.Println("Balance:", ba.balance) 21}
File content for main.go
:
Go1package main 2 3import ( 4 "bankaccount" 5) 6 7func main() { 8 bankAccount := bankaccount.NewBankAccount(1234, 100.0) 9 bankAccount.PrintBalance() 10 // Uncommenting the following line would cause an error because balance is unexported 11 // fmt.Println(bankAccount.balance) 12}
Here, balance
is kept unexported, safeguarding the account's integrity.
Private-like methods in Go are handled through naming conventions. Methods beginning with a lowercase letter are unexported and can't be accessed outside their package.
File content for bankaccount/bankaccount.go
:
Go1package bankaccount 2 3type BankAccount struct { 4 accountNumber int 5 balance float64 6} 7 8// Constructor function for creating new BankAccount 9func NewBankAccount(accountNumber int, balance float64) *BankAccount { 10 return &BankAccount{accountNumber: accountNumber, balance: balance} 11} 12 13// Public method that calls the unexported addInterest method 14func (ba *BankAccount) AddYearlyInterest() { 15 ba.addInterest(0.02) // Adds 2% interest 16} 17 18// Unexported method 19func (ba *BankAccount) addInterest(interestRate float64) { 20 ba.balance += ba.balance * interestRate // Calculation of interest 21} 22 23// Method to print balance (for demonstration purposes) 24func (ba *BankAccount) PrintBalance() { 25 fmt.Println("Balance:", ba.balance) 26}
File content for main.go
:
Go1package main 2 3import ( 4 "bankaccount" 5) 6 7func main() { 8 bankAccount := bankaccount.NewBankAccount(1234, 100.0) 9 bankAccount.AddYearlyInterest() 10 bankAccount.PrintBalance() 11 // Uncommenting the following line would cause an error because addInterest is unexported 12 // bankAccount.addInterest(0.1) 13}
Here, AddYearlyInterest
is an exported method that calls the unexported method addInterest
.
Fantastic work exploring Go's approach to encapsulation through capitalization conventions of struct fields and methods! By understanding and applying these key principles across package boundaries, your Go code becomes robust, secure, and maintainable.
Next, let’s put these concepts into practice. Get ready for some hands-on exercises to solidify what you've learned! Keep coding — exciting challenges await!