Menu
CoddyTech

扩展对象

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

extend 会将模块的方法添加到一个对象,而不是所有实例。扩展类会将这些方法添加到类对象中,因此它们会成为类方法:

module Describable
  def describe
    "#{name} has #{instance_methods(false).size} methods"
  end
end

class Dog
  extend Describable

  def bark
    "Woof"
  end

  def sit
    "sits"
  end
end

puts Dog.describe

输出:

Dog has 2 methods

与 extend 一起使用的模块可以在类的实例变量中保存状态。每个扩展该模块的类都会获得自己的副本,作为类实例变量:

module Counted
  def created
    @created ||= 0
  end

  def record
    @created = created + 1
  end
end

class Order
  extend Counted
end

class Invoice
  extend Counted
end

Order.record
Order.record
Invoice.record
puts "#{Order.created} #{Invoice.created}"

输出:

2 1

extend 也可以作用于单个对象。只有该对象会获得这些方法;同一个类的其他实例保持不变,并且只有扩展后的对象的 is_a? 才是 true:

module Shiny
  def polish
    "#{self} is shining"
  end
end

a = "car"
b = "bike"
a.extend(Shiny)
puts a.polish
puts a.is_a?(Shiny)
puts b.respond_to?(:polish)

输出:

car is shining
true
false

这两个关键字会将同一模块的方法放在不同的位置。include 会将它们变成类的实例方法,extend 会将它们变成被扩展的那个对象的方法:

class Order
  include Printable    # order.print_me       (每个实例)
  extend Counted       # Order.record         (类本身)
end

item.extend(Shiny)     # item.polish          (只有这个对象)
challenge icon

挑战

简单

完成模块 Registry,类会使用 extend 来扩展它。register(name) 会将一个名称添加到该类自己的列表中,列表保存在 @entries 中(首次使用时创建),并返回 Plugin registered x;如果名称已经在列表中,则返回 Plugin already has x。entries 返回列表,count 返回列表的大小。Plugin 和 Theme 都会扩展 Registry,并各自维护独立的列表。

提供的代码会读取 plugin name 或 theme name 格式的行,在对应的类上调用 register,并打印返回内容。最后,它会打印 plugins: a, b (2) 和 themes: ...;如果列表为空,则打印 none (0)。

你的代码写在 registry.rb、plugin.rb 和 theme.rb 中。main.rb 包含提供的输入/输出代码,不能编辑。

亲手试试

require_relative 'registry'
require_relative 'plugin'
require_relative 'theme'

# 提供的输入和输出代码:保持原样
input = []
while (line = gets)
  input << line.chomp
end
input.each do |line|
  kind, name = line.split
  target = kind == "plugin" ? Plugin : Theme
  puts target.register(name)
end
[["plugins", Plugin], ["themes", Theme]].each do |label, klass|
  list = klass.entries.empty? ? "none" : klass.entries.join(", ")
  puts "#{label}: #{list} (#{klass.count})"
end
quiz icon自我测试

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

面向对象编程中的所有课

自己练习: Ruby 在线编译器