状態を保護する
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エラークラスを分けると、クラスごとに1つの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はオブジェクトを読み取り専用にします。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"]チャレンジ
簡単エラークラス InvalidAmount と InsufficientFunds を宣言します。どちらも StandardError のサブクラスです。また、Account を完成させてください。deposit(amount) と withdraw(amount) は、金額が 1 未満の場合、amount must be positive を伴う InvalidAmount を発生させます。金額が残高を上回る場合、withdraw は balance is only 20 を伴う InsufficientFunds を発生させます。エラーが発生した場合、何も変更されません。
提供されたコードは 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オンラインコンパイラ