Menu
CoddyTech

保护状态

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

对象应拒绝会破坏其状态的请求。raise 会在任何内容发生变化之前停止方法。自定义错误类可以准确说明问题;< StandardError 会让这个类成为一种错误(继承,第 5 章):

class InsufficientFunds < StandardError
end

class Account
  attr_reader :balance

  def initialize(balance)
    @balance = balance
  end

  def withdraw(amount)
    raise InsufficientFunds, "only #{balance} available" if amount > balance

    @balance -= amount
  end
end

account = Account.new(50)
begin
  account.withdraw(80)
rescue InsufficientFunds => e
  puts "#{e.class}: #{e.message}"
end
puts account.balance

输出:

InsufficientFunds: only 50 available
50

不同的错误类可以让调用方针对每个问题做出不同的处理,每个类对应一个 rescue。在声明会引发这些错误的类之前,先声明错误类:

class InvalidAmount < StandardError
end

class LimitReached < StandardError
end

class Card
  def charge(amount)
    raise InvalidAmount, "amount must be positive" unless amount.positive?
    raise LimitReached, "limit is 100" if amount > 100

    "charged #{amount}"
  end
end

[40, -5, 500].each do |amount|
  puts Card.new.charge(amount)
rescue InvalidAmount => e
  puts "fix the input: #{e.message}"
rescue LimitReached => e
  puts "try later: #{e.message}"
end

输出:

charged 40
fix the input: amount must be positive
try later: limit is 100

freeze 会将对象设为只读:此后,对其实例变量的任何更改都会引发 FrozenError,但仍可以读取。frozen? 用于判断对象是否已冻结:

class Point
  attr_accessor :x

  def initialize(x)
    @x = x
  end
end

pt = Point.new(1).freeze
puts pt.frozen?
begin
  pt.x = 2
rescue FrozenError => e
  puts e.class
end
puts pt.x

输出:

true
FrozenError
1

如果读取方法返回内部数组,就会直接暴露真实数组,因此外部代码可能会修改它。返回冻结的副本 @items.dup.freeze,或 @items.dup,让对象继续控制自己的数据:

class Playlist
  def initialize
    @songs = []
  end

  def add(song)
    @songs << song
  end

  def songs
    @songs.dup.freeze
  end
end

list = Playlist.new
list.add("Intro")
begin
  list.songs << "Hack"
rescue FrozenError
  puts "songs are read-only"
end
p list.songs

输出:

songs are read-only
["Intro"]
challenge icon

挑战

简单

声明错误类 InvalidAmount 和 InsufficientFunds,它们都是 StandardError 的子类,并完成 Account。金额小于 1 时,deposit(amount) 和 withdraw(amount) 会抛出 InvalidAmount,并使用 amount must be positive 作为错误消息。金额大于余额时,withdraw 会抛出 InsufficientFunds,并使用 balance is only 20 作为错误消息。发生错误时,不会有任何变化。

提供的代码会读取命令 dep n 或 wd n,并在每条命令后输出 balance 70,遇到 InvalidAmount 时输出 invalid: amount must be positive,遇到 InsufficientFunds 时输出 refused: balance is only 20。

你的代码要写在 invalid_amount.rb、insufficient_funds.rb 和 account.rb 中。main.rb 包含提供的输入输出代码,不能编辑。

亲手试试

require_relative 'invalid_amount'
require_relative 'insufficient_funds'
require_relative 'account'

# 提供的输入输出代码:保持原样。
input = []
while (line = gets)
  input << line.chomp
end
account = Account.new
input.each do |line|
  action, amount = line.split
  begin
    action == "dep" ? account.deposit(amount.to_i) : account.withdraw(amount.to_i)
    puts "balance #{account.balance}"
  rescue InvalidAmount => e
    puts "invalid: #{e.message}"
  rescue InsufficientFunds => e
    puts "refused: #{e.message}"
  end
end
quiz icon自我测试

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

面向对象编程中的所有课

自己练习: Ruby 在线编译器