保护状态
本课属于 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 100freeze 会将对象设为只读:此后,对其实例变量的任何更改都会引发 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"]挑战
简单声明错误类 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
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: Ruby 在线编译器