Menu
CoddyTech

Приватные сеттеры

Часть раздела Объектно-ориентированное программирование путешествия по Ruby на Coddy. Урок 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, выводя cannot set balance, когда Ruby отказывает в этом.

Твой код размести в 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