Why encapsulation hides implementation

Free preview 3 min to read

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_fee is internal machinery that withdraw depends on, not part of BankAccount's intended public interface.
  • Leaving internal machinery public lets outside code depend on it by accident, so changing a detail breaks callers.
  • private and protected are Ruby's two tools for narrowing a class's interface on purpose, and the next lessons run both.
Lesson 1 of 7 · free

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.

Type to start searching...