Why encapsulation hides implementation
The BankAccount that's been open this whole time
The Ruby variables & assignment course built this class. Read it again with new eyes:
class BankAccount
def initialize(starting_balance)
@balance = starting_balance
end
def deposit(amount)
@balance += amount
end
def withdraw(amount)
return "insufficient funds" if amount > @balance
@balance -= amount
end
def balance
@balance
end
end
Every method here (initialize, deposit, withdraw, balance) is public, which is Ruby's default for any method not marked otherwise. That was fine, since nothing in the earlier course needed anything else. This course asks a different question: now that a class does real work, which of its methods should code outside the class be allowed to call?
Adding one piece of machinery
Suppose a withdrawal now charges a small fee, calculated by its own method:
class BankAccount
FEE_RATE = 0.02
def withdraw(amount)
return "insufficient funds" if amount > @balance
fee = service_fee(amount)
@balance -= (amount + fee)
fee
end
def service_fee(amount)
(amount * FEE_RATE).round(2)
end
end
account = BankAccount.new(1000)
account.withdraw(100)
# 2.0, the fee charged, and @balance is now 898.0
service_fee does correct work, and withdraw depends on it. But as an ordinary public method, nothing stops unrelated code anywhere in the app from calling account.service_fee(100) directly. That code bypasses withdraw and treats an implementation detail as a feature of the class.
Why that's a problem
service_fee is a calculation withdraw happens to need, not a promise BankAccount makes to the rest of the app. The fee formula might change next month, get renamed, or give way to a flat fee. None of that changes the interface BankAccount means to offer: deposit, withdraw and balance work the same from outside. If some outside code had started calling service_fee, though, the change would break it, and that code never had a reason to reach that far in.
Encapsulation
Encapsulation is deciding which of a class's methods form its public interface and which are internal machinery that happens to be written as methods. Ruby gives you two keywords for narrowing the interface on purpose. private comes in the next lesson, and protected, a related but different tool, a few lessons after that. This course triggers both, NoMethodErrors included, instead of only describing them.
Where this course goes
service_fee becomes the running example for private. balance gets narrowed with protected once there's a reason code outside BankAccount shouldn't read it directly. By the end, BankAccount looks nearly the same from outside, with deposit and withdraw working as before, but its internals are nobody's business but its own.
Key takeaways
- Every method in Ruby is public by default; encapsulation means choosing to narrow that.
service_feeis internal machinery thatwithdrawdepends on, not part ofBankAccount's intended public interface.- Leaving internal machinery public lets outside code depend on it by accident, so changing a detail breaks callers.
privateandprotectedare Ruby's two tools for narrowing a class's interface on purpose, and the next lessons run both.
Keep going with lesson 2
private: no explicit receiver
Marks service_fee private and shows that calls without a receiver work while account.service_fee raises NoMethodError.
A free account also gives you the quizzes, your progress and notes, and the discussion.