プライベートセッター
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プライベートセッターは、クラス内で 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@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
0private attr_accessor :history はリーダーとセッターの両方を隠し、外部のコードからは見えないようにすべき内部状態に使用します。
class Editor
def initialize
self.history = []
end
def type(text)
history << text
end
private attr_accessor :history
endチャレンジ
簡単クラス Wallet を完成させてください。balance は外部から読み取れますが、プライベートセッターを通してのみ設定でき、初期値は 0 です。earn(amount) はその値に加算します。spend(amount) は値を減算して true を返します。または、残高が不足している場合は変更を行わずに false を返します。history は成功した変更の回数を返し、これもプライベートセッターによって保護されます。
提供されているコードは +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
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
自分で練習してみよう: Rubyオンラインコンパイラ