Encapsulation
Encapsulation is one of the fundamental principles of Object-Oriented Programming. It involves bundling data (attributes) and methods that operate on that data within a single unit (class), while restricting direct access to some components to prevent accidental modification.
Understanding Access Modifiers
Section titled “Understanding Access Modifiers”Access modifiers control the visibility and accessibility of class members (attributes and methods).
Access Levels Comparison
Section titled “Access Levels Comparison”class User: def __init__(self, username: str, email: str, password: str): self.username = username # Public attribute self._email = email # Protected attribute (convention) self.__password = password # Private attribute (name mangling)
def public_method(self): """Public method - accessible from anywhere""" return "Public method"
def _protected_method(self): """Protected method - intended for internal use""" return "Protected method"
def __private_method(self): """Private method - name-mangled, harder to access""" return "Private method"Key Differences
Section titled “Key Differences”| Access Level | Syntax | Enforcement | Use Case |
|---|---|---|---|
| Public | attribute | None | Accessible from anywhere |
| Protected | _attribute | Convention only | Internal use, accessible but discouraged |
| Private | __attribute | Name mangling | Truly internal, harder to access accidentally |
public class User { public String username; // Public attribute protected String email; // Protected attribute private String password; // Private attribute
public User(String username, String email, String password) { this.username = username; this.email = email; this.password = password; }
// Public method - accessible from anywhere public String publicMethod() { return "Public method"; }
// Protected method - accessible within package and subclasses protected String protectedMethod() { return "Protected method"; }
// Private method - only accessible within this class private String privateMethod() { return "Private method"; }}Key Differences
Section titled “Key Differences”| Access Level | Syntax | Enforcement | Use Case |
|---|---|---|---|
| Public | public | Enforced | Accessible from anywhere |
| Protected | protected | Enforced | Accessible within package and subclasses |
| Private | private | Enforced | Only accessible within the class |
| Package | (default) | Enforced | Accessible within the same package |
class User { public username: string; // Public attribute protected email: string; // Protected attribute private password: string; // Private attribute
constructor(username: string, email: string, password: string) { this.username = username; this.email = email; this.password = password; }
// Public method - accessible from anywhere public publicMethod(): string { return "Public method"; }
// Protected method - accessible within class and subclasses protected protectedMethod(): string { return "Protected method"; }
// Private method - only accessible within this class private privateMethod(): string { return "Private method"; }}Key Differences
Section titled “Key Differences”| Access Level | Syntax | Enforcement | Use Case |
|---|---|---|---|
| Public | public | Enforced | Accessible from anywhere |
| Protected | protected | Enforced | Accessible within class and subclasses |
| Private | private | Enforced | Only accessible within the class |
#include <string>
class User {public: std::string username; // Public attribute
User(const std::string& username, const std::string& email, const std::string& password) : username(username), email(email), password(password) {}
// Public method - accessible from anywhere std::string publicMethod() { return "Public method"; }
protected: std::string email; // Protected attribute
// Protected method - accessible within class and subclasses std::string protectedMethod() { return "Protected method"; }
private: std::string password; // Private attribute
// Private method - only accessible within this class std::string privateMethod() { return "Private method"; }};Key Differences
Section titled “Key Differences”| Access Level | Syntax | Enforcement | Use Case |
|---|---|---|---|
| Public | public: | Enforced | Accessible from anywhere |
| Protected | protected: | Enforced | Accessible within class and subclasses |
| Private | private: | Enforced | Only accessible within the class |
public class User{ public string username; // Public attribute protected string email; // Protected attribute private string password; // Private attribute
public User(string username, string email, string password) { this.username = username; this.email = email; this.password = password; }
// Public method - accessible from anywhere public string PublicMethod() { return "Public method"; }
// Protected method - accessible within class and subclasses protected string ProtectedMethod() { return "Protected method"; }
// Private method - only accessible within this class private string PrivateMethod() { return "Private method"; }}Key Differences
Section titled “Key Differences”| Access Level | Syntax | Enforcement | Use Case |
|---|---|---|---|
| Public | public | Enforced | Accessible from anywhere |
| Protected | protected | Enforced | Accessible within class and subclasses |
| Private | private | Enforced | Only accessible within the class |
| Internal | internal | Enforced | Accessible within the same assembly |
package demo
// User illustrates visibility: uppercase fields/methods are exported (public-like).type User struct { Username string // exported — visible outside package demo Email string // exported — for parity with examples; constrain with accessors in real code
password string // unexported — only code in package demo may read/write directly}
func NewUser(username, email, password string) *User { return &User{Username: username, Email: email, password: password}}
func (u *User) PublicMethod() string { return "Public method"}
// Same-package “protected” analogue: callers in other packages must use accessors.func (u *User) GetPassword() string { return u.password }
func (*User) privateHelper() string { return "private method" }Key differences in Go
Section titled “Key differences in Go”| Visibility | Syntax | Cross-package rule |
|---|---|---|
| Exported | Username, PublicMethod() | Allowed |
| Unexported | password, privateHelper() | Compiler blocks access |
There is no protected visibility; subclasses in other packages use exported methods or embedding in the same package.
// User illustrates visibility: `pub` fields/methods are public; others are private to the module.pub struct User { pub username: String, // public — visible outside the module pub email: String, // public — for parity with examples; constrain with accessors in real code password: String, // private — only code in this module may read/write directly}
impl User { pub fn new(username: impl Into<String>, email: impl Into<String>, password: impl Into<String>) -> Self { Self { username: username.into(), email: email.into(), password: password.into(), } }
pub fn public_method(&self) -> &'static str { "Public method" }
pub fn get_password(&self) -> &str { &self.password }
fn private_helper(&self) -> &'static str { "private method" }}Key differences in Rust
Section titled “Key differences in Rust”| Visibility | Syntax | Cross-module rule |
|---|---|---|
| Public | pub username, pub fn public_method() | Allowed |
| Private | password, fn private_helper() | Compiler blocks access |
There is no protected visibility; subtypes in other modules use public methods or shared module scope.
Protected Attributes/Members
Section titled “Protected Attributes/Members”Protected members are intended for internal use but can be accessed by subclasses.
class User: def __init__(self, username: str, password: str): self.username = username self._password = password # Protected attribute
def get_password(self): return self._password
def set_password(self, new_password: str): self._password = new_password
user = User("john_doe", "secret123")print(user._password) # Works, but not recommendedprint(user.get_password()) # Preferred waypublic class User { protected String password; // Protected attribute
public User(String username, String password) { this.password = password; }
public String getPassword() { return password; }
public void setPassword(String newPassword) { this.password = newPassword; }}
// In same package or subclasspublic class AdminUser extends User { public AdminUser(String username, String password) { super(username, password); }
public void resetPassword() { // Can access protected member this.password = "new_password"; }}class User { protected password: string; // Protected attribute
constructor(username: string, password: string) { this.password = password; }
getPassword(): string { return this.password; }
setPassword(newPassword: string): void { this.password = newPassword; }}
// In subclassclass AdminUser extends User { resetPassword(): void { // Can access protected member this.password = "new_password"; }}#include <string>
class User {protected: std::string password; // Protected attribute
public: User(const std::string& username, const std::string& password) : password(password) {}
std::string getPassword() const { return password; }
void setPassword(const std::string& newPassword) { password = newPassword; }};
// In subclassclass AdminUser : public User {public: AdminUser(const std::string& username, const std::string& password) : User(username, password) {}
void resetPassword() { // Can access protected member password = "new_password"; }};public class User{ protected string password; // Protected attribute
public User(string username, string password) { this.password = password; }
public string GetPassword() { return password; }
public void SetPassword(string newPassword) { password = newPassword; }}
// In subclasspublic class AdminUser : User{ public AdminUser(string username, string password) : base(username, password) {}
public void ResetPassword() { // Can access protected member password = "new_password"; }}package main
import "fmt"
type User struct { password string}
func NewUser(password string) *User { return &User{password: password}}
func (u *User) GetPassword() string { return u.password }func (u *User) SetPassword(pw string) { u.password = pw }
type AdminUser struct { User}
func NewAdminUser(password string) *AdminUser { return &AdminUser{User: *NewUser(password)}}
func (a *AdminUser) ResetPassword() { a.password = "new_password" // embedding in same package: can access promoted field within package}
func main() { u := NewUser("secret123") fmt.Println(u.GetPassword())
admin := NewAdminUser("x") admin.ResetPassword() fmt.Println(admin.GetPassword())}struct User { password: String,}
impl User { fn new(password: impl Into<String>) -> Self { Self { password: password.into(), } }
fn get_password(&self) -> &str { &self.password }
fn set_password(&mut self, password: impl Into<String>) { self.password = password.into(); }}
struct AdminUser { user: User,}
impl AdminUser { fn new(password: impl Into<String>) -> Self { Self { user: User::new(password), } }
fn reset_password(&mut self) { self.user.set_password("new_password"); }
fn get_password(&self) -> &str { self.user.get_password() }}
fn main() { let u = User::new("secret123"); println!("{}", u.get_password());
let mut admin = AdminUser::new("x"); admin.reset_password(); println!("{}", admin.get_password());}Private Attributes/Members
Section titled “Private Attributes/Members”Private members are truly internal and should not be accessed from outside the class.
class User: def __init__(self, username: str, password: str): self.username = username self.__password = password # Private attribute (name-mangled)
def get_password(self): return self.__password # Accessible within class
user = User("john_doe", "secret123")# print(user.__password) # AttributeError: 'User' object has no attribute '__password'print(user.get_password()) # Works: "secret123"# print(user._User__password) # Works but DON'T DO THIS: "secret123"public class User { private String password; // Private attribute
public User(String username, String password) { this.password = password; }
public String getPassword() { return password; // Accessible within class }
// Private method private String hashPassword(String password) { // Internal implementation return "hashed_" + password; }}
// Usagepublic class Main { public static void main(String[] args) { User user = new User("john_doe", "secret123"); // System.out.println(user.password); // Compile error: password has private access System.out.println(user.getPassword()); // Works: "secret123" }}class User { private password: string; // Private attribute
constructor(username: string, password: string) { this.password = password; }
getPassword(): string { return this.password; // Accessible within class }
// Private method private hashPassword(password: string): string { // Internal implementation return "hashed_" + password; }}
// Usageconst user = new User("john_doe", "secret123");// console.log(user.password); // Compile error: password is privateconsole.log(user.getPassword()); // Works: "secret123"#include <iostream>#include <string>
class User {private: std::string password; // Private attribute
// Private method std::string hashPassword(const std::string& password) { // Internal implementation return "hashed_" + password; }
public: User(const std::string& username, const std::string& password) : password(password) {}
std::string getPassword() const { return password; // Accessible within class }};
int main() { User user("john_doe", "secret123"); // std::cout << user.password; // Compile error: password is private std::cout << user.getPassword() << std::endl; // Works: "secret123"
return 0;}using System;
public class User{ private string password; // Private attribute
public User(string username, string password) { this.password = password; }
public string GetPassword() { return password; // Accessible within class }
// Private method private string HashPassword(string password) { // Internal implementation return "hashed_" + password; }}
class Program{ static void Main() { User user = new User("john_doe", "secret123"); // Console.WriteLine(user.password); // Compile error: password is private Console.WriteLine(user.GetPassword()); // Works: "secret123" }}package main
import "fmt"
type User struct { password string}
func NewUser(username, pw string) *User { return &User{password: pw}}
func (u *User) GetPassword() string { return u.password}
func main() { u := NewUser("john_doe", "secret123") fmt.Println(u.GetPassword())}secret123struct User { password: String,}
impl User { fn new(_username: impl Into<String>, password: impl Into<String>) -> Self { Self { password: password.into(), } }
fn get_password(&self) -> &str { &self.password }}
fn main() { let u = User::new("john_doe", "secret123"); println!("{}", u.get_password());}<Aside type="tip" title="Rust “private”">- Fields and methods without `pub` are **private** outside the module (`password`, `hash_password`).- Consumers in other modules only see public methods like `get_password`.</Aside>secret123
Properties: The Pythonic Way
Section titled “Properties: The Pythonic Way”In Python, you usually don’t write getters/setters unless you need validation or transformation. Properties provide a clean way to add getters/setters.
class User: def __init__(self, username: str, email: str, password: str): self.username = username self.email = email self._password = password # Protected attribute
@property def password(self): """Getter - accessed like an attribute""" return self._password
@password.setter def password(self, new_password: str): """Setter - accessed like attribute assignment""" if len(new_password) < 8: raise ValueError("Password must be at least 8 characters") self._password = new_password
user.password = "new_password" # Clean syntax, uses setterprint(user.password) # Clean syntax, uses getter# user.password = "short" # Raises ValueErrorpublic class User { private String password;
public User(String username, String email, String password) { this.password = password; }
// Getter public String getPassword() { return password; }
// Setter with validation public void setPassword(String newPassword) { if (newPassword.length() < 8) { throw new IllegalArgumentException("Password must be at least 8 characters"); } this.password = newPassword; }}
// Usagepublic class Main { public static void main(String[] args) { user.setPassword("new_password"); // Uses setter System.out.println(user.getPassword()); // Uses getter // user.setPassword("short"); // Throws IllegalArgumentException }}class User { private _password: string;
constructor(username: string, email: string, password: string) { this._password = password; }
// Getter get password(): string { return this._password; }
// Setter with validation set password(newPassword: string) { if (newPassword.length < 8) { throw new Error("Password must be at least 8 characters"); } this._password = newPassword; }}
user.password = "new_password"; // Clean syntax, uses setterconsole.log(user.password); // Clean syntax, uses getter// user.password = "short"; // Throws Error#include <iostream>#include <string>#include <stdexcept>
class User {private: std::string password;
public: User(const std::string& username, const std::string& email, const std::string& password) : password(password) {}
// Getter std::string getPassword() const { return password; }
// Setter with validation void setPassword(const std::string& newPassword) { if (newPassword.length() < 8) { throw std::invalid_argument("Password must be at least 8 characters"); } password = newPassword; }};
int main() { user.setPassword("new_password"); // Uses setter std::cout << user.getPassword() << std::endl; // Uses getter // user.setPassword("short"); // Throws exception
return 0;}using System;
public class User{ private string _password;
public User(string username, string email, string password) { _password = password; }
// Property with getter and setter public string Password { get { return _password; } set { if (value.Length < 8) { throw new ArgumentException("Password must be at least 8 characters"); } _password = value; } }}
class Program{ static void Main() { user.Password = "new_password"; // Clean syntax, uses setter Console.WriteLine(user.Password); // Clean syntax, uses getter // user.Password = "short"; // Throws ArgumentException }}package main
import ( "errors" "fmt")
type User struct { username string email string password string}
func NewUser(username, email, password string) *User { return &User{username: username, email: email, password: password}}
func (u *User) Password() string { return u.password }
func (u *User) SetPassword(newPassword string) error { if len(newPassword) < 8 { return errors.New("Password must be at least 8 characters") } u.password = newPassword return nil}
func main() { if err := user.SetPassword("new_password"); err != nil { panic(err) } fmt.Println(user.Password())}struct User { username: String, email: String, password: String,}
impl User { fn new(username: impl Into<String>, email: impl Into<String>, password: impl Into<String>) -> Self { Self { username: username.into(), email: email.into(), password: password.into(), } }
fn password(&self) -> &str { &self.password }
fn set_password(&mut self, new_password: impl Into<String>) -> Result<(), String> { let new_password = new_password.into(); if new_password.len() < 8 { return Err("Password must be at least 8 characters".into()); } self.password = new_password; Ok(()) }}
fn main() { user.set_password("new_password").unwrap(); println!("{}", user.password());}Encapsulation in Practice
Section titled “Encapsulation in Practice”Bad Example: No Encapsulation
Section titled “Bad Example: No Encapsulation”class BankAccount: def __init__(self, initial_balance: float): self.balance = initial_balance # Public attribute - dangerous!
def deposit(self, amount: float): self.balance += amount
def withdraw(self, amount: float): self.balance -= amount
account = BankAccount(1000.0)account.balance = -500.0 # Direct modification - unsafe!print(account.balance) # -500.0 (incorrect in banking!)public class BankAccount { public double balance; // Public attribute - dangerous!
public BankAccount(double initialBalance) { this.balance = initialBalance; }
public void deposit(double amount) { balance += amount; }
public void withdraw(double amount) { balance -= amount; }}
// Usagepublic class Main { public static void main(String[] args) { BankAccount account = new BankAccount(1000.0); account.balance = -500.0; // Direct modification - unsafe! System.out.println(account.balance); // -500.0 (incorrect in banking!) }}class BankAccount { public balance: number; // Public attribute - dangerous!
constructor(initialBalance: number) { this.balance = initialBalance; }
deposit(amount: number): void { this.balance += amount; }
withdraw(amount: number): void { this.balance -= amount; }}
const account = new BankAccount(1000.0);account.balance = -500.0; // Direct modification - unsafe!console.log(account.balance); // -500.0 (incorrect in banking!)#include <iostream>
class BankAccount {public: double balance; // Public attribute - dangerous!
BankAccount(double initialBalance) : balance(initialBalance) {}
void deposit(double amount) { balance += amount; }
void withdraw(double amount) { balance -= amount; }};
int main() { BankAccount account(1000.0); account.balance = -500.0; // Direct modification - unsafe! std::cout << account.balance << std::endl; // -500.0 (incorrect in banking!)
return 0;}using System;
public class BankAccount{ public double balance; // Public attribute - dangerous!
public BankAccount(double initialBalance) { balance = initialBalance; }
public void Deposit(double amount) { balance += amount; }
public void Withdraw(double amount) { balance -= amount; }}
class Program{ static void Main() { BankAccount account = new BankAccount(1000.0); account.balance = -500.0; // Direct modification - unsafe! Console.WriteLine(account.balance); // -500.0 (incorrect in banking!) }}package main
import "fmt"
type BankAccount struct { Balance float64 // exported field — anyone can break invariants}
func NewBankAccount(initial float64) *BankAccount { return &BankAccount{Balance: initial}}
func (a *BankAccount) Deposit(amount float64) { a.Balance += amount }func (a *BankAccount) Withdraw(amount float64) { a.Balance -= amount }
func main() { account := NewBankAccount(1000.0) account.Balance = -500.0 fmt.Println(account.Balance)}-500struct BankAccount { pub balance: f64, // public field — anyone can break invariants}
impl BankAccount { fn new(initial: f64) -> Self { Self { balance: initial } }
fn deposit(&mut self, amount: f64) { self.balance += amount; }
fn withdraw(&mut self, amount: f64) { self.balance -= amount; }}
fn main() { let mut account = BankAccount::new(1000.0); account.balance = -500.0; println!("{}", account.balance);}-500Good Example: Proper Encapsulation
Section titled “Good Example: Proper Encapsulation”class BankAccount: def __init__(self, initial_balance: float): self.__balance = initial_balance # Private attribute
def deposit(self, amount: float): """Deposit money with validation""" if amount > 0: self.__balance += amount else: raise ValueError("Deposit amount must be positive")
def withdraw(self, amount: float): """Withdraw money with validation""" if amount <= 0: raise ValueError("Withdrawal amount must be positive") if amount > self.__balance: raise ValueError("Insufficient funds") self.__balance -= amount
@property def balance(self): """Read-only access to balance""" return self.__balance
account = BankAccount(1000.0)account.deposit(500.0)account.withdraw(200.0)print(account.balance) # 1300.0
# account.__balance = -500.0 # Won't affect actual balance (name mangling)# account.balance = -500.0 # AttributeError: can't set attributepublic class BankAccount { private double balance; // Private attribute
public BankAccount(double initialBalance) { this.balance = initialBalance; }
// Deposit money with validation public void deposit(double amount) { if (amount > 0) { this.balance += amount; } else { throw new IllegalArgumentException("Deposit amount must be positive"); } }
// Withdraw money with validation public void withdraw(double amount) { if (amount <= 0) { throw new IllegalArgumentException("Withdrawal amount must be positive"); } if (amount > this.balance) { throw new IllegalArgumentException("Insufficient funds"); } this.balance -= amount; }
// Read-only access to balance public double getBalance() { return balance; }}
// Usagepublic class Main { public static void main(String[] args) { BankAccount account = new BankAccount(1000.0); account.deposit(500.0); account.withdraw(200.0); System.out.println(account.getBalance()); // 1300.0
// account.balance = -500.0; // Compile error: balance has private access }}class BankAccount { private _balance: number; // Private attribute
constructor(initialBalance: number) { this._balance = initialBalance; }
deposit(amount: number): void { if (amount > 0) { this._balance += amount; } else { throw new Error("Deposit amount must be positive"); } }
withdraw(amount: number): void { if (amount <= 0) { throw new Error("Withdrawal amount must be positive"); } if (amount > this._balance) { throw new Error("Insufficient funds"); } this._balance -= amount; }
// Read-only access to balance get balance(): number { return this._balance; }}
const account = new BankAccount(1000.0);account.deposit(500.0);account.withdraw(200.0);console.log(account.balance); // 1300.0
// account._balance = -500.0; // Compile error: _balance is private// account.balance = -500.0; // Compile error: balance has only a getter#include <iostream>#include <stdexcept>
class BankAccount {private: double balance; // Private attribute
public: BankAccount(double initialBalance) : balance(initialBalance) {}
void deposit(double amount) { if (amount > 0) { balance += amount; } else { throw std::invalid_argument("Deposit amount must be positive"); } }
void withdraw(double amount) { if (amount <= 0) { throw std::invalid_argument("Withdrawal amount must be positive"); } if (amount > balance) { throw std::invalid_argument("Insufficient funds"); } balance -= amount; }
// Read-only access to balance double getBalance() const { return balance; }};
int main() { BankAccount account(1000.0); account.deposit(500.0); account.withdraw(200.0); std::cout << account.getBalance() << std::endl; // 1300.0
// account.balance = -500.0; // Compile error: balance is private
return 0;}using System;
public class BankAccount{ private double _balance; // Private attribute
public BankAccount(double initialBalance) { _balance = initialBalance; }
public void Deposit(double amount) { if (amount > 0) { _balance += amount; } else { throw new ArgumentException("Deposit amount must be positive"); } }
public void Withdraw(double amount) { if (amount <= 0) { throw new ArgumentException("Withdrawal amount must be positive"); } if (amount > _balance) { throw new ArgumentException("Insufficient funds"); } _balance -= amount; }
// Read-only property public double Balance { get { return _balance; } }}
class Program{ static void Main() { BankAccount account = new BankAccount(1000.0); account.Deposit(500.0); account.Withdraw(200.0); Console.WriteLine(account.Balance); // 1300.0
// account.Balance = -500.0; // Compile error: Balance is read-only }}package main
import ( "errors" "fmt")
type BankAccount struct { balance float64}
func NewBankAccount(initial float64) *BankAccount { return &BankAccount{balance: initial}}
func (a *BankAccount) Deposit(amount float64) error { if amount <= 0 { return errors.New("Deposit amount must be positive") } a.balance += amount return nil}
func (a *BankAccount) Withdraw(amount float64) error { if amount <= 0 { return errors.New("Withdrawal amount must be positive") } if amount > a.balance { return errors.New("Insufficient funds") } a.balance -= amount return nil}
func (a *BankAccount) Balance() float64 { return a.balance }
func main() { account := NewBankAccount(1000.0) _ = account.Deposit(500.0) _ = account.Withdraw(200.0) fmt.Println(account.Balance())}1300struct BankAccount { balance: f64,}
impl BankAccount { fn new(initial: f64) -> Self { Self { balance: initial } }
fn deposit(&mut self, amount: f64) -> Result<(), String> { if amount <= 0.0 { return Err("Deposit amount must be positive".into()); } self.balance += amount; Ok(()) }
fn withdraw(&mut self, amount: f64) -> Result<(), String> { if amount <= 0.0 { return Err("Withdrawal amount must be positive".into()); } if amount > self.balance { return Err("Insufficient funds".into()); } self.balance -= amount; Ok(()) }
fn balance(&self) -> f64 { self.balance }}
fn main() { let mut account = BankAccount::new(1000.0); account.deposit(500.0).unwrap(); account.withdraw(200.0).unwrap(); println!("{}", account.balance());}1300Visual Representation
Section titled “Visual Representation”Key Takeaways
Section titled “Key Takeaways”Remember: In Python, encapsulation is more about convention and design than strict enforcement. In Java, encapsulation is enforced by the compiler. The goal is to create clear interfaces and prevent accidental misuse.