强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Ruby 入门指南 / 第 11 章:元编程

第 11 章:元编程

“元编程是编写能够编写代码的代码。”


11.1 元编程概述

11.1.1 什么是元编程

元编程(Metaprogramming)是指程序能够将代码作为数据来处理,能够在运行时创建或修改类、方法和属性。

特性说明示例
反射运行时检查对象信息.methods.class
动态方法运行时定义方法define_method
方法缺失捕获未定义方法调用method_missing
类修改运行时修改类重新打开类
代码求值动态执行代码字符串evalinstance_eval
钩子响应类事件inheritedincluded

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 动手练习

  1. 实现 attr_validator
# 实现一个验证器 DSL
class Model
  extend ActiveModel::Validations
  attribute :name
  validates :name, presence: true, length: { minimum: 2 }
end
  1. 实现 AutoDelegator
# 自动将未定义的方法委托给实例变量
class AutoDelegator
  # 你的代码...
end
  1. 实现配置 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执行字符串代码,注意安全风险
钩子inheritedincludedmethod_added 响应事件
最佳实践优先使用简单方案,只在必要时使用元编程

📖 扩展阅读


上一章← 第 10 章:块与迭代器 下一章第 12 章:异常处理 →