Menu
Coddy logo textTech

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

Часть раздела Объектно-ориентированное программирование путешествия по 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