Menu
Coddy logo textTech

Protección del estado

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

Un objeto debería rechazar las solicitudes que romperían su estado. raise detiene el método antes de que cambie nada. Tu propia clase de error identifica el problema con precisión; < StandardError hace que la clase sea un tipo de error (herencia, capítulo 5):

class InsufficientFunds < StandardError
end

class Account
  attr_reader :balance

  def initialize(balance)
    @balance = balance
  end

  def withdraw(amount)
    raise InsufficientFunds, "only #{balance} available" if amount > balance

    @balance -= amount
  end
end

account = Account.new(50)
begin
  account.withdraw(80)
rescue InsufficientFunds => e
  puts "#{e.class}: #{e.message}"
end
puts account.balance

Salida:

InsufficientFunds: only 50 available
50

Las clases de error separadas permiten que el llamador reaccione de manera diferente a cada problema, con un rescue por clase. Declara las clases de error antes de las clases que las generan:

class InvalidAmount < StandardError
end

class LimitReached < StandardError
end

class Card
  def charge(amount)
    raise InvalidAmount, "amount must be positive" unless amount.positive?
    raise LimitReached, "limit is 100" if amount > 100

    "charged #{amount}"
  end
end

[40, -5, 500].each do |amount|
  puts Card.new.charge(amount)
rescue InvalidAmount => e
  puts "fix the input: #{e.message}"
rescue LimitReached => e
  puts "try later: #{e.message}"
end

Salida:

charged 40
fix the input: amount must be positive
try later: limit is 100

freeze convierte un objeto en de solo lectura: después de hacerlo, cualquier cambio en sus variables de instancia genera FrozenError, mientras que la lectura sigue funcionando. frozen? indica si un objeto está congelado:

class Point
  attr_accessor :x

  def initialize(x)
    @x = x
  end
end

pt = Point.new(1).freeze
puts pt.frozen?
begin
  pt.x = 2
rescue FrozenError => e
  puts e.class
end
puts pt.x

Salida:

true
FrozenError
1

Un lector que devuelve un arreglo interno entrega el arreglo real, por lo que el código externo podría modificarlo. Devuelve una copia congelada con @items.dup.freeze o @items.dup para mantener el control del objeto sobre sus propios datos:

class Playlist
  def initialize
    @songs = []
  end

  def add(song)
    @songs << song
  end

  def songs
    @songs.dup.freeze
  end
end

list = Playlist.new
list.add("Intro")
begin
  list.songs << "Hack"
rescue FrozenError
  puts "songs are read-only"
end
p list.songs

Salida:

songs are read-only
["Intro"]
challenge icon

Desafío

Fácil

Declara las clases de error InvalidAmount e InsufficientFunds, ambas subclases de StandardError, y completa Account. deposit(amount) y withdraw(amount) generan InvalidAmount con amount must be positive para cantidades inferiores a 1. withdraw genera InsufficientFunds con balance is only 20 cuando la cantidad es mayor que el saldo. No cambia nada cuando se genera un error.

El código proporcionado lee comandos dep n o wd n e imprime balance 70 después de cada uno, invalid: amount must be positive para InvalidAmount y refused: balance is only 20 para InsufficientFunds.

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

Pruébalo tú mismo

require_relative 'invalid_amount'
require_relative 'insufficient_funds'
require_relative 'account'

# Código de entrada/salida suministrado: déjalo tal como está
input = []
while (line = gets)
  input << line.chomp
end
account = Account.new
input.each do |line|
  action, amount = line.split
  begin
    action == "dep" ? account.deposit(amount.to_i) : account.withdraw(amount.to_i)
    puts "balance #{account.balance}"
  rescue InvalidAmount => e
    puts "invalid: #{e.message}"
  rescue InsufficientFunds => e
    puts "refused: #{e.message}"
  end
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