Что такое рефлексия и как ее использовать?ruby-69

Рефлексия (reflection) — это способность программы исследовать и модифицировать свою собственную структуру и поведение во время выполнения. Ruby как динамический язык предоставляет богатые возможности для рефлексии.

Основные возможности рефлексии в Ruby

1. Интроспекция

class User
  attr_accessor :name, :email

  def initialize(name, email)
    @name = name
    @email = email
  end

  def send_email
    # отправка email
  end
end

# Получение информации о классе
User.class              # => Class
User.methods            # список методов класса
User.instance_methods   # список методов экземпляра
User.ancestors          # цепочка наследования

2. Динамическое создание и вызов методов

# Динамическое создание метода
class User
  define_method :greet do |name|
    "Hello, #{name}!"
  end
end

user = User.new
user.greet("Alice")     # => "Hello, Alice!"

# Динамический вызов метода
method_name = :send_email
user.send(method_name)  # вызовет метод send_email

3. Работа с переменными экземпляра

user = User.new("Bob", "bob@example.com")

# Получение списка переменных экземпляра
user.instance_variables # => [:@name, :@email]

# Чтение/запись переменных
user.instance_variable_get(:@name)  # => "Bob"
user.instance_variable_set(:@name, "Robert")

Практические примеры использования

1. Динамические finders

class Repository
  def self.method_missing(method, *args)
    if method.to_s.start_with?('find_by_')
      attribute = method.to_s[8..-1]
      ->(value) { all.find { |item| item.send(attribute) == value } }
    else
      super
    end
  end
end

2. Сериализация объектов

class ObjectSerializer
  def serialize(obj)
    obj.instance_variables.each_with_object({}) do |var, hash|
      hash[var.to_s.delete('@')] = obj.instance_variable_get(var)
    end
  end
end

3. Плагины и расширения

module Loggable
  def self.included(base)
    base.instance_methods(false).each do |method|
      base.send(:define_method, "#{method}_with_logging") do |*args|
        puts "Calling #{method} with #{args.inspect}"
        send("#{method}_without_logging", *args)
      end

      base.alias_method "#{method}_without_logging", method
      base.alias_method method, "#{method}_with_logging"
    end
  end
end

Опасности и ограничения

  1. Производительность:

    • Рефлексивные вызовы медленнее статических
    • Пример: send на ```40% медленнее прямого вызова
  2. Безопасность:

    • Динамическое выполнение кода может быть опасным
    • Всегда проверяйте входные данные при использовании send/public_send
  3. Читаемость кода:

    • Чрезмерное использование рефлексии ухудшает понимание кода
    • Документируйте сложные рефлексивные решения

Продвинутые техники

1. Метаклассы и eigenclass

obj = Object.new

# Добавление метода только к конкретному экземпляру
class << obj
  def special_method
    "I'm special!"
  end
end

2. Парсинг исходного кода

С использованием RubyVM::AbstractSyntaxTree:

ast = RubyVM::AbstractSyntaxTree.parse(<<```RUBY)
  def hello(name)
    puts "Hello, \#{name}!"
  end
RUBY

ast.children # анализ структуры метода

3. Хуки жизненного цикла

module Trace
  def self.included(base)
    base.extend(ClassMethods)
  end

  module ClassMethods
    def method_added(method)
      puts "Method #{method} added to #{self}"
    end
  end
end

Резюмируем

Рефлексия в Ruby позволяет:

  • Исследовать структуру классов и объектов во время выполнения
  • Динамически создавать и модифицировать код
  • Реализовывать сложные метапрограммные конструкции
  • Создавать гибкие API (как в Rails ActiveRecord)

Основные методы:

  • send/public_send - динамический вызов методов
  • define_method - создание методов на лету
  • instance_variable_get/set - работа с переменными
  • method_missing - обработка "потерянных" методов

Используйте рефлексию:

  1. Когда нужно уменьшить дублирование кода
  2. Для создания DSL (предметно-ориентированных языков)
  3. В библиотеках и фреймворках

Избегайте:

  1. Чрезмерного использования в бизнес-логике
  2. Непроверенных динамических вызовов
  3. Когда есть более простые статические решения