Защита состояния
Часть раздела Объектно-ориентированное программирование путешествия по Ruby на Coddy. Урок 20 из 57.
Объект должен отклонять запросы, которые нарушили бы его состояние. raise останавливает метод до того, как что-либо изменится. Ваш собственный класс ошибки точно называет проблему; < StandardError делает этот класс разновидностью ошибки (наследование, глава 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Результат:
InsufficientFunds: only 50 available
50Отдельные классы ошибок позволяют вызывающему коду по-разному реагировать на каждую проблему, используя по одному rescue для каждого класса. Объявляйте классы ошибок до классов, которые их вызывают:
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Вывод:
charged 40
fix the input: amount must be positive
try later: limit is 100freeze делает объект доступным только для чтения: после этого любое изменение его переменных экземпляра вызывает FrozenError, а чтение по-прежнему работает. frozen? сообщает, заморожен ли объект:
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Вывод:
true
FrozenError
1Метод чтения, возвращающий внутренний массив, выдаёт настоящий массив, поэтому внешний код может изменить его. Верните замороженную копию с помощью @items.dup.freeze или @items.dup, чтобы объект сохранял контроль над собственными данными:
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Вывод:
songs are read-only
["Intro"]Задание
ЛегкоОбъявите классы ошибок InvalidAmount и InsufficientFunds, оба являющиеся подклассами StandardError, и завершите реализацию Account. deposit(amount) и withdraw(amount) должны вызывать InvalidAmount с сообщением amount must be positive для значений меньше 1. withdraw должен вызывать InsufficientFunds с сообщением balance is only 20, когда сумма больше баланса. При возникновении ошибки ничего не изменяется.
Предоставленный код считывает команды dep n или wd n и после каждой выводит balance 70, для InvalidAmount — invalid: amount must be positive, а для InsufficientFunds — refused: balance is only 20.
Ваш код должен находиться в файлах invalid_amount.rb, insufficient_funds.rb и account.rb. В main.rb содержится предоставленный код ввода/вывода, и его нельзя редактировать.
Попробуйте сами
require_relative 'invalid_amount'
require_relative 'insufficient_funds'
require_relative 'account'
# Предоставленный код ввода/вывода: оставьте его как есть
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
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Классы и объекты
Работа с файламиОпределение классовПеременные экземпляраМетоды и selfto_s и inspectПовторение — читательский билет4Инкапсуляция
Приватные методыЗащищённые методыПриватные сеттерыЗащита состоянияПовторение — торговый автомат7Полиморфизм и утиная типизация
ПолиморфизмУтиная типизацияАбстрактные методыcase и классыПовторение — способы оплаты10Шаблоны проектирования, часть 2
Делегирование с помощью ForwardableШаблон декоратораШаблон строителяШаблон состояния2Аксессоры и self
Чтение и записьПользовательские сеттерыself в методахИдентичность и равенствоПовторение — список контактов5Наследование
ПодклассыПереопределение методовВызов superis_a? и предкиПовторение — расчёт зарплаты сотрудников8Специальные методы
Равенство и хешированиеМетоды операторовИндексация и добавлениеПользовательские коллекцииПовторение — дроби11Проект: Управление библиотекой
Книги и читателиВыдача книг3Методы и переменные классов
Методы классовПеременные экземпляра классаКонстантыФабричные методыПовторение — коды купонов6Модули и примеси
Модули и includeРасширение объектовМодуль ComparableМодуль EnumerableПовторение — спортивная лига9Паттерны проектирования. Часть 1
Паттерн «Одиночка»Фабричный паттернПаттерн «Наблюдатель»Паттерн «Стратегия»Потренируйтесь самостоятельно: Онлайн-компилятор Ruby