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 |
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"; }}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" }}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 = User("john", "john@example.com", "old_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 user = new User("john", "john@example.com", "old_password"); user.setPassword("new_password"); // Uses setter System.out.println(user.getPassword()); // Uses getter // user.setPassword("short"); // Throws IllegalArgumentException }}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!) }}Good 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 }}Visual 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.