Menu
CoddyTech

الأساليب المجردة

جزء من قسم البرمجة كائنية التوجه في رحلة 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

إلى جانب الأساليب المجرّدة، يمكن للقالب أن يوفّر أساليب الخطاف: خطوات ذات تنفيذ افتراضي يمكن للفئات الفرعية تجاوزها. تُعدّ footer أعلاه أسلوب خطاف؛ وكذلك أسلوب نعم أو لا مثل 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 عبر الإنترنت