Menu
CoddyTech

Duck typing

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 33 trên 57.

Ruby không kiểm tra lớp của một đối tượng trước khi gọi phương thức, nó chỉ cần đối tượng đó có phương thức ấy. “Nếu nó đi như vịt và kêu như vịt thì nó là vịt.” Đây là kiểu vịt: các lớp không có lớp cha chung vẫn có thể được dùng ở cùng một vị trí:

class Duck
  def speak
    "Quack"
  end
end

class Robot
  def speak
    "Beep"
  end
end

class Radio
  def speak
    "...news..."
  end
end

[Duck.new, Robot.new, Radio.new].each { |thing| puts thing.speak }

Đầu ra:

Quack
Beep
...news...

Các lớp có sẵn cũng tham gia. Một phương thức chỉ dùng << sẽ hoạt động với mảng, chuỗi hoặc bất kỳ đối tượng nào của bạn định nghĩa <<:

class Tally
  attr_reader :count

  def initialize
    @count = 0
  end

  def <<(_item)
    @count += 1
    self
  end
end

def export(items, target)
  items.each { |item| target << item.to_s }
  target
end

p export([1, 2], [])
p export([1, 2], +"")
puts export([1, 2, 3], Tally.new).count

Đầu ra:

["1", "2"]
"12"
3

respond_to?(:name) kiểm tra xem một đối tượng có phương thức công khai hay không mà không gọi phương thức đó. Dùng cách này khi một đối tượng có thể hỗ trợ hoặc không hỗ trợ một tính năng tùy chọn:

class Printer
  def print_page(text)
    "printing #{text}"
  end
end

class Scanner
end

[Printer.new, Scanner.new].each do |device|
  if device.respond_to?(:print_page)
    puts device.print_page("report")
  else
    puts "#{device.class} cannot print"
  end
end

Đầu ra:

printing report
Scanner cannot print

Khi đối tượng không có phương thức đó, lời gọi sẽ gây ra NoMethodError, thông báo lỗi này nêu tên phương thức và đối tượng. Duck typing dựa vào việc mọi đối tượng được truyền vào đều trả lời được các phương thức mà nó nhận được:

def loudest(things)
  things.map(&:speak).max_by(&:size)
end

class Cat
  def speak
    "Meow"
  end
end

puts loudest([Cat.new, Cat.new])
begin
  loudest([Cat.new, 42])
rescue NoMethodError => e
  puts e.message
end

Đầu ra:

Meow
undefined method `speak' for an instance of Integer
challenge icon

Thử thách

Dễ

Hoàn thiện các lớp Printer và LineCounter cùng hàm hỗ trợ export(items, target) trong functions.rb. export gửi từng phần tử đến target bằng target << item.to_s rồi trả về target. Printer#<< in văn bản kèm số dòng theo dạng 1: text rồi trả về printer. LineCounter#<< đếm số dòng và ký tự rồi trả về bộ đếm; to_s trả về 3 lines, 12 chars.

Code được cung cấp đọc các phần tử rồi xuất chúng ra một mảng (được in bằng p), một chuỗi mới (được in bằng p), một Printer và một LineCounter (được in bằng puts).

Code của bạn nằm trong printer.rb, line_counter.rb và functions.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 'printer'
require_relative 'line_counter'
require_relative 'functions'

# Đoạn code đầu vào/đầu ra đã được cung cấp: giữ nguyên nhé
input = []
while (line = gets)
  input << line.chomp
end
p export(input, [])
p export(input, +"")
export(input, Printer.new)
puts export(input, LineCounter.new)
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