Menu
Coddy logo textTech

Setters privados

Parte da seção Programação orientada a objetos do Journey de Ruby da Coddy. Lição 19 de 57.

Frequentemente, um valor deve ser legível por todos, mas alterável apenas pelo objeto. attr_reader fornece o leitor público, e private attr_writer, um setter que somente o objeto pode usar. Isso funciona porque attr_writer retorna os nomes dos 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

Saída:

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

Um setter privado é chamado com self. dentro da classe, o único receptor que o Ruby aceita para métodos privados. Sem self., a atribuição apenas criaria uma variável 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

Saída:

true

Passar por um setter privado em vez de atribuir @balance diretamente mantém as regras em um só lugar, como uma verificação de que o valor é 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

Saída:

50
0

private attr_accessor :history oculta tanto o leitor quanto o setter, para um estado interno que o código externo nem deveria ver:

class Editor
  def initialize
    self.history = []
  end

  def type(text)
    history << text
  end

  private attr_accessor :history
end
challenge icon

Desafio

Fácil

Complete a classe Wallet. balance pode ser lido externamente, mas só pode ser definido por meio de um setter privado, começando em 0. earn(amount) adiciona a ele. spend(amount) subtrai e retorna true, ou retorna false sem fazer alterações quando o saldo é insuficiente. history retorna o número de alterações bem-sucedidas, também mantido por trás de um setter privado.

O código fornecido lê comandos +n ou -n, imprimindo balance 30 após cada um, ou declined quando um gasto falha. Em seguida, imprime changes: 2 e tenta wallet.balance = 1000000, imprimindo cannot set balance quando Ruby recusa.

Seu código deve estar em wallet.rb. main.rb contém o código fornecido de entrada/saída e não pode ser editado.

Experimente você mesmo

require_relative 'wallet'

# Código de entrada/saída fornecido: mantenha-o 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 iconTeste seus conhecimentos

Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.

Todas as lições de Programação orientada a objetos

Pratique por conta própria: Compilador de Ruby online