Menu
CoddyTech

Asignadores privados

Parte de la sección Programación orientada a objetos del Journey de Ruby de Coddy. Lección 19 de 57.

A menudo, un valor debería ser legible para todos, pero modificable solo por el objeto. attr_reader proporciona el lector público, y private attr_writer un setter que solo puede usar el objeto. Esto funciona porque attr_writer devuelve los nombres de los métodos que define:

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

Salida:

30
private method `balance=' called for an instance of Wallet

Se llama a un setter privado con self. dentro de la clase, el único receptor que Ruby acepta para los métodos privados. Sin self., la asignación solo crearía una variable local:

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

Salida:

true

Pasar por un establecedor privado en lugar de asignar directamente @balance mantiene las reglas en un solo lugar, como una comprobación de que el valor sea válido:

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

Salida:

50
0

private attr_accessor :history oculta tanto el lector como el setter, para un estado interno que el código externo ni siquiera debería ver:

class Editor
  def initialize
    self.history = []
  end

  def type(text)
    history << text
  end

  private attr_accessor :history
end
challenge icon

Desafío

Fácil

Completa la clase Wallet. balance se puede leer desde fuera, pero solo se establece mediante un setter privado y comienza en 0. earn(amount) lo incrementa. spend(amount) lo disminuye y devuelve true, o devuelve false sin hacer cambios cuando el saldo es demasiado bajo. history devuelve el número de cambios exitosos y también se mantiene detrás de un setter privado.

El código proporcionado lee comandos +n o -n e imprime balance 30 después de cada uno, o declined cuando falla un gasto. Después imprime changes: 2 e intenta wallet.balance = 1000000, e imprime cannot set balance cuando Ruby lo rechaza.

Tu código va en wallet.rb. main.rb contiene el código de entrada/salida proporcionado y no se puede editar.

Pruébalo tú mismo

require_relative 'wallet'

# Código de entrada/salida proporcionado: mantenlo tal como está
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 iconPonte a prueba

Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.

Todas las lecciones de Programación orientada a objetos

Practica por tu cuenta: Compilador de Ruby online