Encapsulaton & Information Hiding
Encapsulation and Information Hiding
Encapsulation bundles an object’s data (attributes) and behaviour (methods) into a single unit, and information hiding restricts access to that internal state.
By controlling who can read or modify an object’s internals, we preserve its integrity, prevent invalid states, and improve maintainability.
Access Modifiers
- public: Everyone can access this member. Use for methods intended as part of the class’s public API.
- protected: Accessible within the class and its subclasses (and same package in Java). Use for members needed by derived classes but hidden from the outside world.
- private: Only the defining class can access. Use for internal data that must never be modified directly.
Choosing the right access level helps reduce unintended interference or modification with an object’s internals, and helps ensure the object remains in a valid state.
| Scope / Example | Private | Protected | Public |
|---|---|---|---|
| Python Example | self.__isbn |
self._isbn |
self.isbn |
| Java Example | private String isbn; |
protected String isbn; |
public String isbn; |
| Accessible to: | |||
| Class | Yes | Yes | Yes |
| Subclass | No | Yes | Yes |
| Package | No | Yes (Java) | Yes |
| Other Classes | No | No | Yes |
Notes: In Python, underscores signal intent (_isbn internal; __isbn is name-mangled to _ClassName__isbn), not strict access control.
In Java, protected is visible to subclasses and to classes in the same package.
Encapsulation with Parent → Child Classes
Python uses naming conventions: public (no underscore), _protected (by convention), __private (name-mangled). Child class accesses _balance but not __pin.
class Account:
def __init__(self, owner: str, opening: float):
self.owner = owner # public
self._balance = opening # "protected" by convention
self.__pin = "1234" # private (name-mangled)
# public API
def deposit(self, amt: float) -> bool:
if amt > 0:
self._balance += amt
return True
return False
def withdraw(self, amt: float) -> bool:
if 0 < amt <= self._balance:
self._balance -= amt
return True
return False
def get_balance(self) -> float:
return self._balance
class SavingsAccount(Account):
def __init__(self, owner: str, opening: float, rate: float):
super().__init__(owner, opening)
self._rate = rate # "protected" detail
# public behavior that legitimately uses the protected state
def apply_interest(self):
self._balance += self._balance * self._rate
# --- Demo ---
if __name__ == "__main__":
a = Account("Alice", 1000)
a.deposit(250); a.withdraw(300)
print(a.owner, "balance:", a.get_balance()) # a.owner: public access ... not recommended
s = SavingsAccount("Bob", 500, 0.05)
s.apply_interest()
s.withdraw(50)
print(s.owner, "balance:", s.get_balance())
# print(a.__pin) # AttributeError: name-mangled (private)
# print(s._balance) # possible but discouraged: "protected" by convention
Java enforces modifiers: private hides state, protected for subclasses, public for API. Includes a psvm to instantiate and test.
class Account {
public String owner; // public access
protected double balance; // visible to subclasses
public Account(String owner, double opening) {
this.owner = owner;
this.balance = opening;
}
// public API
public String getOwner() { return owner; }
public double getBalance() { return balance; }
public boolean deposit(double amt) {
if (amt <= 0) return false;
balance += amt;
return true;
}
public boolean withdraw(double amt) {
if (amt <= 0 || amt > balance) return false;
balance -= amt;
return true;
}
}
class SavingsAccount extends Account {
private double interestRate; // subclass-specific detail
public SavingsAccount(String owner, double opening, double rate) {
super(owner, opening);
this.interestRate = rate;
}
public void applyInterest() { // public behavior using protected balance
balance += balance * interestRate;
}
}
public class EncapsulationDemo {
public static void main(String[] args) {
Account a = new Account("Alice", 1000);
a.deposit(250);
a.withdraw(300);
System.out.printf("%s balance: %.2f%n", a.getOwner(), a.getBalance());
SavingsAccount s = new SavingsAccount("Bob", 500, 0.05);
s.applyInterest();
s.withdraw(50);
System.out.printf("%s balance: %.2f%n", s.getOwner(), s.getBalance());
a.owner = "Eve"; // a.owner: public access ... not recommended
// The following would NOT compile if uncommented:
// s.interestRate = 0.10; // interestRate is private
// Accessing 'balance' directly outside subclasses is discouraged.
// It's 'protected': allowed only to subclasses (and same-package), not general API.
}
}
Risks of Bypassing Encapsulation
- Someone might set your
balanceto a negative value, breaking your business rules. - Direct access to “private” members can lead to fragile code that breaks when internals change.
- Encapsulation prevents unintended side-effects and makes refactoring safer.
Key Takeaways
- Encapsulation hides internal state; information hiding enforces class invariants.
public,protected, andprivatecontrol who can see or modify members.- Always expose a minimal, well-defined public interface to reduce coupling.
- By preventing direct access, you guard against invalid states and simplify maintenance.