Menu
Coddy logo textTech

プライベートセッター

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
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 は外部から読み取れますが、プライベートセッターを通してのみ設定でき、初期値は 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
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン

自分で練習してみよう: Rubyオンラインコンパイラ