Menu
CoddyTech

Bảo vệ trạng thái

Thuộc phần Lập trình hướng đối tượng trong hành trình Ruby của Coddy. Bài học 20 trên 57.

Đối tượng nên từ chối những yêu cầu có thể làm hỏng trạng thái của nó. raise dừng phương thức trước khi có bất kỳ thay đổi nào. Tên lớp lỗi do bạn tạo giúp nêu chính xác vấn đề; < StandardError khiến lớp này trở thành một loại lỗi (kế thừa (inheritance), chương 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

Đầu ra:

InsufficientFunds: only 50 available
50

Các lớp lỗi riêng biệt cho phép bên gọi xử lý từng vấn đề theo cách khác nhau, với một rescue cho mỗi lớp. Khai báo các lớp lỗi trước những lớp phát sinh lỗi đó:

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

Đầu ra:

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

freeze chuyển một đối tượng sang chế độ chỉ đọc: sau đó, mọi thay đổi đối với các biến thực thể của đối tượng đều gây ra FrozenError, còn việc đọc vẫn hoạt động. frozen? cho biết một đối tượng có bị đóng băng hay không:

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

Đầu ra:

true
FrozenError
1

Một phương thức đọc trả về mảng nội bộ sẽ đưa ra mảng thật, nên code bên ngoài có thể thay đổi mảng đó. Hãy trả về một bản sao đã đóng băng bằng @items.dup.freeze, hoặc @items.dup, để đối tượng tự kiểm soát dữ liệu của mình:

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

Đầu ra:

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

Thử thách

Dễ

Khai báo các lớp lỗi InvalidAmount và InsufficientFunds, cả hai đều là lớp con của StandardError, rồi hoàn thiện Account. deposit(amount) và withdraw(amount) phát sinh InvalidAmount với thông báo amount must be positive khi số tiền nhỏ hơn 1. withdraw phát sinh InsufficientFunds với thông báo balance is only 20 khi số tiền lớn hơn số dư. Số dư không thay đổi khi có lỗi phát sinh.

Code được cung cấp đọc các lệnh dep n hoặc wd n và in ra balance 70 sau mỗi lệnh, invalid: amount must be positive cho InvalidAmount và refused: balance is only 20 cho InsufficientFunds.

Đặt code của bạn trong invalid_amount.rb, insufficient_funds.rb và account.rb. main.rb chứa code đầu vào/đầu ra được cung cấp và bạn không thể chỉnh sửa tệp này.

Tự làm thử

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

# Đoạn code đầu vào/đầu ra có sẵn: giữ nguyên
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 iconTự kiểm tra

Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.

Tất cả bài học trong Lập trình hướng đối tượng

Tự luyện tập: Trình biên dịch Ruby online