Menu
CoddyTech

プライベートセッター

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

private setter は、クラス内で self. を付けて呼び出します。これは Ruby が private メソッドに対して受け入れる唯一のレシーバーです。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オンラインコンパイラ