Menu
Coddy logo textTech
flag Ar iconالعربيةdown icon

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

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