Menu
CoddyTech

多态

本课属于 Coddy Ruby 学习旅程的“面向对象编程”单元。第 32 课,共 57 课。

多态意味着,对不同类的对象调用同一个方法时,每个对象都会以自己的方式响应。遍历各种形状的代码会对每个形状调用 area,而不需要询问它是什么形状:

class Shape
  def to_s
    format("%s %.2f", self.class.name.downcase, area)
  end
end

class Circle < Shape
  def initialize(r)
    @r = r
  end

  def area
    3.14159 * @r * @r
  end
end

class Square < Shape
  def initialize(side)
    @side = side
  end

  def area
    @side * @side
  end
end

shapes = [Circle.new(1), Square.new(3)]
shapes.each { |s| puts s }
puts format("total %.2f", shapes.sum(&:area))

输出:

circle 3.14
square 9.00
total 12.14

没有多态时,调用方必须检查每个对象的类型,并在每个需要区分的地方重复检查。有了多态,这种差异就由各个类内部处理:

# 不使用多态
case shape.kind
when "circle" then 3.14159 * shape.r ** 2
when "square" then shape.side ** 2
end

# 使用多态
shape.area

这样,添加一种新对象就不需要更改调用代码:只要创建一个带有自己 area 的新子类,它就能用于所有现有的循环:

class Shape
  def describe
    "area #{area}"
  end
end

class Square < Shape
  def area
    4
  end
end

class Triangle < Shape
  def area
    6
  end
end

[Square.new, Triangle.new].each { |s| puts s.describe }

输出:

area 4
area 6

to_s 是一个常见的例子:puts 会对任何对象调用它,而且每个类的响应都不同。你可以通过定义自己的 to_s,让你的类也参与其中:

class Coin
  def to_s
    "a coin"
  end
end

[42, "text", nil, [1, 2], Coin.new].each { |x| puts "[#{x}]" }

输出:

[42]
[text]
[]
[[1, 2]]
[a coin]
challenge icon

挑战

简单

完成这些形状。Shape#to_s 返回 circle: 3.14,即小写的类名和保留两位小数的面积,每个子类都要提供 area:Circle.new(r)(圆周率使用 3.14159)、Rectangle.new(w, h) 和 Triangle.new(base, height)。

提供的代码会读取 circle 1、rectangle 2 3 或 triangle 4 5 这样的行,打印每个形状,然后输出 total: 22.14 和 largest: rectangle: 6.00,整个过程只使用 to_s 和 area。

你的代码要写在 shape.rb、circle.rb、rectangle.rb 和 triangle.rb 中。main.rb 包含提供的输入/输出代码,不能编辑。

亲手试试

require_relative 'shape'
require_relative 'circle'
require_relative 'rectangle'
require_relative 'triangle'

# 提供的输入/输出代码:保持原样
input = []
while (line = gets)
  input << line.chomp
end
kinds = { "circle" => Circle, "rectangle" => Rectangle, "triangle" => Triangle }
shapes = input.map do |line|
  kind, *numbers = line.split
  kinds[kind].new(*numbers.map(&:to_f))
end
shapes.each { |s| puts s }
puts format("total: %.2f", shapes.sum(&:area))
puts "largest: #{shapes.max_by(&:area)}"
quiz icon自我测试

这节课包含一个小测验。开始学习即可作答,并记录你的进度。

面向对象编程中的所有课

自己练习: Ruby 在线编译器