Menu
CoddyTech

הגנה על מצב האובייקט

חלק מהיחידה תכנות מונחה עצמים במסלול ה-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 100

freeze הופכת אובייקט לקריאה בלבד: לאחר מכן, כל שינוי במשתני המופע שלו מעלה את 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"]
challenge icon

אתגר

קל

הצהירו על מחלקות השגיאה 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 אחרי כל אחת מהן, invalid: amount must be positive עבור InvalidAmount ו־refused: balance is only 20 עבור InsufficientFunds.

הקוד שלך צריך להופיע בקבצים 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
quiz iconבחנו את עצמכם

השיעור הזה כולל חידון קצר. התחילו את השיעור כדי לענות עליו ולעקוב אחרי ההתקדמות.

כל השיעורים ביחידה תכנות מונחה עצמים

תרגלו בעצמכם: קומפיילר Ruby אונליין