Menu
CoddyTech

私有设置方法

本课属于 Coddy Ruby 学习旅程的“面向对象编程”单元。第 19 课,共 57 课。

通常,某个值应该对所有人都可读,但只有对象才能修改。attr_reader 提供公共读取器,而 private attr_writer 提供只有对象才能使用的设置器。这是因为 attr_writer 会返回它定义的方法名称:

class Wallet
  attr_reader :balance

  def initialize
    self.balance = 0
  end

  def earn(amount)
    self.balance = balance + amount
  end

  private attr_writer :balance
end

w = Wallet.new
w.earn(30)
puts w.balance
begin
  w.balance = 1_000_000
rescue NoMethodError => e
  puts e.message
end

输出:

30
private method `balance=' called for an instance of Wallet

私有 setter 在类内部通过 self. 调用,这是 Ruby 允许私有方法使用的唯一接收者。如果没有 self.,赋值只会创建一个局部变量:

class Lamp
  attr_reader :on

  def initialize
    self.on = false
  end

  def toggle
    self.on = !on
    self
  end

  private

  attr_writer :on
end

puts Lamp.new.toggle.toggle.toggle.on

输出:

true

通过私有 setter 进行赋值,而不是直接给 @balance 赋值,可以将规则集中在一处,例如检查值是否有效:

class Tank
  attr_reader :litres

  def initialize(capacity)
    @capacity = capacity
    self.litres = 0
  end

  def fill(amount)
    self.litres = litres + amount
  end

  def drain(amount)
    self.litres = litres - amount
  end

  private

  def litres=(value)
    @litres = value.clamp(0, @capacity)
  end
end

t = Tank.new(50)
t.fill(80)
puts t.litres
t.drain(70)
puts t.litres

输出:

50
0

private attr_accessor :history 会隐藏读取器和设置器,用于存储外部代码甚至不应看到的内部状态:

class Editor
  def initialize
    self.history = []
  end

  def type(text)
    history << text
  end

  private attr_accessor :history
end
challenge icon

挑战

简单

完成 Wallet 类。balance 可以从外部读取,但只能通过私有 setter 设置,初始值为 0。earn(amount) 会增加它的值。spend(amount) 会扣除相应的值并返回 true;如果余额不足,则不做任何更改并返回 false。history 返回成功更改的次数,也通过私有 setter 设置。

提供的代码会读取 +n 或 -n 命令,每次操作后打印 balance 30,如果扣款失败则打印 declined。然后,它会打印 changes: 2 并尝试执行 wallet.balance = 1000000,当 Ruby 拒绝此操作时打印 cannot set balance。

你的代码要写在 wallet.rb 中。main.rb 包含提供的输入/输出代码,不能修改。

亲手试试

require_relative 'wallet'

# 已提供的输入输出代码:保持原样。
input = []
while (line = gets)
  input << line.chomp
end
wallet = Wallet.new
input.each do |cmd|
  amount = cmd[1..].to_i
  if cmd.start_with?("+")
    wallet.earn(amount)
    puts "balance #{wallet.balance}"
  elsif wallet.spend(amount)
    puts "balance #{wallet.balance}"
  else
    puts "declined"
  end
end
puts "changes: #{wallet.history}"
begin
  wallet.balance = 1_000_000
  puts "balance set"
rescue NoMethodError
  puts "cannot set balance"
end
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: Ruby 在线编译器