Menu
Coddy logo textTech

שיטות מופשטות

חלק מהיחידה תכנות מונחה עצמים במסלול ה-Ruby של Coddy. שיעור 34 מתוך 57.

מחלקת־על יכולה להגדיר את שלבי האלגוריתם ולהשאיר חלק מהשלבים למחלקות־המשנה שלה. זהו דפוס שיטת התבנית: render שלהלן קבועה, ואילו header ו־row משתנות מדוח לדוח:

class Report
  def render(items)
    [header, *items.map { |i| row(i) }, footer].join("\n")
  end

  def footer
    "(#{self.class.name})"
  end
end

class CsvReport < Report
  def header
    "name,price"
  end

  def row(item)
    item.join(",")
  end
end

puts CsvReport.new.render([["pen", 2], ["cup", 5]])

פלט:

name,price
pen,2
cup,5
(CsvReport)

מחלקת העל עדיין צריכה לציין אילו מתודות תת־מחלקה חייבת לכתוב. המוסכמה היא להשתמש במתודה מופשטת שמעלה NotImplementedError עם הודעה ברורה:

class Report
  def render(items)
    [header, *items.map { |i| row(i) }].join("\n")
  end

  def header
    raise NotImplementedError, "#{self.class} must implement header"
  end

  def row(_item)
    raise NotImplementedError, "#{self.class} must implement row"
  end
end

class HtmlReport < Report
  def header
    "[table]"
  end
end

begin
  puts HtmlReport.new.render([1])
rescue NotImplementedError => e
  puts e.message
end

פלט:

HtmlReport must implement row

NotImplementedError אינה StandardError, ולכן rescue ללא ציון חריגות לא יתפוס אותה. כך נמנע מצב שבו טיפול כללי בשגיאות יבלע מתודה חסרה; כשצריך, יש לתפוס אותה לפי שמה:

def risky
  raise NotImplementedError, "missing step"
end

begin
  begin
    risky
  rescue => e
    puts "general handler: #{e.message}"
  end
rescue NotImplementedError => e
  puts "caught by name: #{e.message}"
end
puts NotImplementedError.ancestors.include?(StandardError)

פלט:

caught by name: missing step
false

לצד מתודות אבסטרקטיות, תבנית יכולה להציע מתודות hook: שלבים עם ברירת מחדל שמחלקות יורשות יכולות לדרוס. footer שלמעלה היא hook; וכך גם מתודה של כן או לא כמו include_total?:

class Report
  def render(items)
    lines = [header, *items.map { |i| row(i) }]
    lines << total_line(items) if include_total?
    lines.join("\n")
  end

  def include_total?     # נקודת הרחבה: תשובת ברירת המחדל
    false
  end
end
challenge icon

אתגר

קל

השלם את מחלקות הדוח. Report#render(items) מחזירה את שורת הכותרת, שורת row אחת לכל פריט ואת footer, כשהן מחוברות באמצעות תווי שורה חדשה. header ו־row(item) הן מופשטות: הן מעלות את NotImplementedError עם HtmlReport must implement row (שם המחלקה והמתודה). footer היא נקודת הרחבה שמחזירה את end of report. ל־CsvReport יש את הכותרת name,qty ואת השורות pen,3. ל־TextReport יש את הכותרת NAME QTY, שורות שבהן השם מרופד ל־8 תווים (pen 3), ואת הכותרת התחתונה 2 items. HtmlReport מגדירה רק את הכותרת [table].

הקוד שסופק קורא את סוג הדוח מהשורה הראשונה ואת הפריטים כשורות בתבנית name qty, ואז מדפיס את render או את הודעת השגיאה.

יש לכתוב את הקוד שלך בקבצים report.rb, csv_report.rb, text_report.rb ו־html_report.rb. הקובץ main.rb מכיל את קוד הקלט/פלט שסופק ואי אפשר לערוך אותו.

נסו בעצמכם

require_relative 'report'
require_relative 'csv_report'
require_relative 'text_report'
require_relative 'html_report'

# קוד קלט/פלט שסופק: יש להשאיר אותו כפי שהוא
input = []
while (line = gets)
  input << line.chomp
end
kinds = { "csv" => CsvReport, "text" => TextReport, "html" => HtmlReport }
report = kinds[input[0]].new
items = input.drop(1).map(&:split)
begin
  puts report.render(items)
rescue NotImplementedError => e
  puts e.message
end
quiz iconבחנו את עצמכם

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

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

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