Ruby 入门指南 / 第 11 章:元编程
第 11 章:元编程
“元编程是编写能够编写代码的代码。”
11.1 元编程概述
11.1.1 什么是元编程
元编程(Metaprogramming)是指程序能够将代码作为数据来处理,能够在运行时创建或修改类、方法和属性。
| 特性 | 说明 | 示例 |
|---|---|---|
| 反射 | 运行时检查对象信息 | .methods、.class |
| 动态方法 | 运行时定义方法 | define_method |
| 方法缺失 | 捕获未定义方法调用 | method_missing |
| 类修改 | 运行时修改类 | 重新打开类 |
| 代码求值 | 动态执行代码字符串 | eval、instance_eval |
| 钩子 | 响应类事件 | inherited、included |
11.2 反射机制
11.2.1 对象内省
# 查看对象的方法
"hello".methods.sort # 所有方法
"hello".public_methods.sort # 公共方法
"hello".private_methods.sort # 私有方法
"hello".protected_methods # 保护方法
# 查看特定方法
"hello".respond_to?(:upcase) # => true
"hello".respond_to?(:delete) # => true
# 方法详情
m = "hello".method(:upcase)
m.name # => :upcase
m.arity # => 0(参数数量)
m.source_location # => ["...", 行号]
# 对象属性
obj = "hello"
obj.class # => String
obj.object_id # => 唯一 ID
obj.instance_variables # => []
obj.instance_variable_get(:@name) # => nil
11.2.2 类的内省
class Animal; end
class Dog < Animal; end
# 类的继承链
Dog.ancestors # => [Dog, Animal, Object, Kernel, BasicObject]
Dog.superclass # => Animal
Dog < Animal # => true
Dog < Object # => true
# 类的方法来源
Dog.instance_methods(false) # => Dog 自己定义的方法
Animal.instance_methods(false) # => Animal 自己定义的方法
# 检查方法定义位置
Dog.instance_method(:to_s).source_location
11.3 动态方法定义
11.3.1 define_method
class User
# 动态定义多个属性访问器
%i[name email age].each do |attr|
define_method(attr) do
instance_variable_get("@#{attr}")
end
define_method("#{attr}=") do |value|
instance_variable_set("@#{attr}", value)
end
end
def initialize(name, email, age)
@name = name
@email = email
@age = age
end
end
user = User.new("Alice", "[email protected]", 25)
user.name # => "Alice"
user.age # => 25
11.3.2 批量定义方法
class Validator
# 动态生成验证方法
TYPES = {
string: String,
integer: Integer,
array: Array,
hash: Hash
}.freeze
TYPES.each do |type_name, type_class|
define_method("validate_#{type_name}") do |value|
unless value.is_a?(type_class)
raise TypeError, "Expected #{type_name}, got #{value.class}"
end
true
end
end
end
v = Validator.new
v.validate_string("hello") # => true
v.validate_integer(42) # => true
v.validate_string(123) # => TypeError
11.3.3 带块的方法定义
class Callbacks
def self.define_callback(name, &block)
define_method(name) do |*args|
puts "[#{Time.now}] Calling #{name}"
result = block.call(*args)
puts "[#{Time.now}] #{name} completed"
result
end
end
define_callback(:process) do |data|
data.map(&:upcase)
end
define_callback(:validate) do |input|
!input.nil? && !input.empty?
end
end
cb = Callbacks.new
cb.process(["hello", "world"])
# [2024-01-15 10:30:00] Calling process
# [2024-01-15 10:30:00] process completed
11.4 method_missing
11.4.1 基本用法
class DynamicGreeter
def method_missing(name, *args)
if name.to_s.start_with?("greet_in_")
language = name.to_s.sub("greet_in_", "")
case language
when "english" then "Hello, #{args.first}!"
when "chinese" then "你好,#{args.first}!"
when "japanese" then "こんにちは、#{args.first}!"
else "Hello (#{language}), #{args.first}!"
end
else
super
end
end
def respond_to_missing?(name, include_private = false)
name.to_s.start_with?("greet_in_") || super
end
end
greeter = DynamicGreeter.new
greeter.greet_in_english("Alice") # => "Hello, Alice!"
greeter.greet_in_chinese("Alice") # => "你好,Alice!"
greeter.greet_in_french("Alice") # => "Hello (french), Alice!"
greeter.respond_to?(:greet_in_english) # => true
greeter.respond_to?(:unknown_method) # => false
11.4.2 委托代理
class Proxy
def initialize(target)
@target = target
@log = []
end
def method_missing(name, *args, &block)
@log << { method: name, args: args, time: Time.now }
if @target.respond_to?(name)
@target.send(name, *args, &block)
else
super
end
end
def respond_to_missing?(name, include_private = false)
@target.respond_to?(name, include_private) || super
end
def call_log
@log
end
end
# 使用代理记录所有方法调用
array = Proxy.new([3, 1, 4, 1, 5])
array.sort # => [1, 1, 3, 4, 5]
array.first # => 1
array.call_log # => [{method: :sort, ...}, {method: :first, ...}]
11.4.3 Hash 风格访问
class OpenStruct
def initialize(hash = {})
@data = hash
end
def method_missing(name, *args)
key = name.to_s
if key.end_with?("=")
# setter
@data[key.chomp("=").to_sym] = args.first
elsif key.end_with?("?")
# 布尔查询
!!@data[key.chomp("?").to_sym]
elsif @data.key?(name)
# getter
@data[name]
else
super
end
end
def respond_to_missing?(name, include_private = false)
@data.key?(name.to_s.chomp("=").to_sym) || super
end
end
user = OpenStruct.new(name: "Alice", age: 25)
user.name # => "Alice"
user.email = "[email protected]"
user.email # => "[email protected]"
user.admin? # => false
11.5 eval 家族
11.5.1 eval
# eval - 执行字符串代码(谨慎使用!)
eval("1 + 2") # => 3
eval("puts 'hello'") # => hello
# eval 可以访问当前作用域的变量
x = 10
eval("x + 5") # => 15
# ⚠️ eval 的安全风险
# 永远不要对用户输入使用 eval!
user_input = "system('rm -rf /')"
# eval(user_input) # 危险!!!
# 安全的替代方案
# 1. 使用 send
# 2. 使用 public_send
# 3. 使用白名单验证
11.5.2 instance_eval / class_eval
# instance_eval - 在对象上下文中执行
class MyClass
def initialize
@secret = 42
end
end
obj = MyClass.new
obj.instance_eval { @secret } # => 42
obj.instance_eval { @secret = 100 }
obj.instance_eval { @secret } # => 100
# class_eval (module_eval) - 在类上下文中执行
String.class_eval do
def custom_method
"Custom: #{self}"
end
end
"hello".custom_method # => "Custom: hello"
# 在 class_eval 中定义方法
class User
%i[name email age].each do |attr|
class_eval <<-RUBY, __FILE__, __LINE__ + 1
def #{attr}
@#{attr}
end
def #{attr}=(value)
@#{attr} = value
end
RUBY
end
end
user = User.new
user.name = "Alice"
user.name # => "Alice"
11.5.3 class_exec / module_exec
# class_exec - 在类上下文中执行块
module Validations
def validates(*attributes, **options)
attributes.each do |attr|
class_exec(attr, options) do |attribute, opts|
define_method("validate_#{attribute}") do
value = instance_variable_get("@#{attribute}")
if opts[:presence] && (value.nil? || value.to_s.empty?)
raise "#{attribute} is required"
end
if opts[:length] && value.is_a?(String)
min, max = opts[:length].values_at(:minimum, :maximum)
if min && value.length < min
raise "#{attribute} too short (min #{min})"
end
if max && value.length > max
raise "#{attribute} too long (max #{max})"
end
end
end
end
end
end
end
class User
extend Validations
validates :name, presence: true, length: { minimum: 2, maximum: 50 }
validates :email, presence: true
attr_accessor :name, :email
end
user = User.new
user.name = "A"
user.validate_name rescue puts $!.message # "name too short (min 2)"
11.6 钩子方法
11.6.1 类级钩子
module Tracker
def self.included(base)
puts "#{self} included into #{base}"
base.extend(ClassMethods)
end
def self.extended(base)
puts "#{self} extended into #{base}"
end
module ClassMethods
def inherited(subclass)
puts "#{self} inherited by #{subclass}"
super
end
def method_added(method_name)
puts "Method #{method_name} added to #{self}"
end
def method_removed(method_name)
puts "Method #{method_name} removed from #{self}"
end
def singleton_method_added(method_name)
puts "Singleton method #{method_name} added to #{self}"
end
end
end
class Animal
include Tracker
def speak
"..."
end
end
# Output:
# Tracker included into Animal
# Method speak added to Animal
class Dog < Animal
def bark
"Woof!"
end
end
# Output:
# Animal inherited by Dog
# Method bark added to Dog
11.6.2 const_missing
class Config
def self.const_missing(name)
case name
when :DATABASE_URL
ENV["DATABASE_URL"] || "sqlite3://db.sqlite3"
when :API_KEY
ENV["API_KEY"] || raise("API_KEY not set")
when :DEBUG
ENV["DEBUG"] == "true"
else
super
end
end
end
puts Config::DATABASE_URL # 从环境变量读取
puts Config::DEBUG # => false
11.6.3 method_missing + const_missing 组合
class ActiveRecord
class Base
def self.find_by(column_name, value)
# 模拟数据库查询
puts "Finding by #{column_name} = #{value}"
end
def self.method_missing(name, *args)
if name.to_s.start_with?("find_by_")
columns = name.to_s.sub("find_by_", "").split("_and_")
if columns.length == args.length
conditions = columns.zip(args).to_h
puts "Finding by #{conditions}"
return
end
end
super
end
def self.respond_to_missing?(name, include_private = false)
name.to_s.start_with?("find_by_") || super
end
end
end
class User < ActiveRecord::Base; end
User.find_by_name("Alice")
# Finding by name = Alice
User.find_by_name_and_email("Alice", "[email protected]")
# Finding by {"name"=>"Alice", "email"=>"[email protected]"}
11.7 实际业务场景
11.7.1 DSL 配置
class AppConfig
class << self
attr_reader :settings
def configure(&block)
@settings ||= {}
instance_exec(&block)
end
def method_missing(name, *args)
if name.to_s.end_with?("=")
key = name.to_s.chomp("=").to_sym
@settings[key] = args.first
elsif args.empty? && @settings&.key?(name)
@settings[name]
else
super
end
end
end
end
AppConfig.configure do
self.app_name = "MyApp"
self.version = "1.0.0"
self.debug = false
end
puts AppConfig.app_name # => "MyApp"
11.7.2 自动注册
class HandlerRegistry
@handlers = {}
def self.register(type, &block)
@handlers[type] = block
end
def self.handle(type, *args)
handler = @handlers[type]
raise "Unknown handler: #{type}" unless handler
handler.call(*args)
end
def self.inherited(subclass)
subclass.instance_variable_set(:@handlers, {})
super
end
end
class EmailHandler < HandlerRegistry
register :welcome do |user|
"Welcome email to #{user}"
end
register :reset_password do |user, token|
"Reset password for #{user} with token #{token}"
end
end
EmailHandler.handle(:welcome, "Alice")
# => "Welcome email to Alice"
11.7.3 ActiveRecord 风格属性
module AttributeDSL
def attribute(name, type: :string, default: nil)
# 定义 getter
define_method(name) do
value = instance_variable_get("@#{name}")
value.nil? ? default : value
end
# 定义 setter
define_method("#{name}=") do |value|
converted = case type
when :integer then value.to_i
when :float then value.to_f
when :string then value.to_s
when :boolean then !!value
when :symbol then value.to_sym
else value
end
instance_variable_set("@#{name}", converted)
end
# 定义布尔查询
define_method("#{name}?") do
!!instance_variable_get("@#{name}")
end
end
end
class User
extend AttributeDSL
attribute :name, type: :string
attribute :age, type: :integer, default: 0
attribute :active, type: :boolean, default: true
end
user = User.new
user.name = "Alice"
user.age = "25" # 自动转为整数
user.name # => "Alice"
user.age # => 25
user.active? # => true
11.8 元编程的陷阱
11.8.1 性能问题
# ❌ 每次调用都 eval
def slow_method
eval("[1,2,3].map { |n| n * 2 }")
end
# ✅ 使用 define_method
def fast_method
[1, 2, 3].map { |n| n * 2 }
end
# method_missing 有性能开销
# 高频调用的方法应该明确定义
11.8.2 可读性问题
# ❌ 难以理解的元编程
class Mystery
def method_missing(name, *args)
@data.send(name, *args)
end
end
# ✅ 清晰的委托
require "delegate"
class UserCollection < DelegateClass(Array)
# 有意义的方法定义
end
11.8.3 调试困难
# 元编程生成的方法在堆栈追踪中不明显
# 使用 __method__ 和 caller 追踪
def debuggable_method
puts "Called: #{__method__}"
puts "From: #{caller.first}"
end
11.9 动手练习
- 实现
attr_validator
# 实现一个验证器 DSL
class Model
extend ActiveModel::Validations
attribute :name
validates :name, presence: true, length: { minimum: 2 }
end
- 实现
AutoDelegator
# 自动将未定义的方法委托给实例变量
class AutoDelegator
# 你的代码...
end
- 实现配置 DSL
# 实现类似 Rails 的配置 DSL
App.configure do
config.database.host = "localhost"
config.database.port = 5432
config.cache.ttl = 3600
end
11.10 本章小结
| 要点 | 说明 |
|---|---|
| 反射 | .methods、.respond_to? 检查对象能力 |
| define_method | 动态定义方法 |
| method_missing | 捕获未定义方法,实现动态分发 |
| eval | 执行字符串代码,注意安全风险 |
| 钩子 | inherited、included、method_added 响应事件 |
| 最佳实践 | 优先使用简单方案,只在必要时使用元编程 |
📖 扩展阅读
上一章:← 第 10 章:块与迭代器 下一章:第 12 章:异常处理 →