Как работает динамический вызов методов?ruby-70

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

Основные способы динамического вызова

1. Метод send

Базовый способ вызова метода по имени:

class User
  def greet(name)
    "Hello, #{name}!"
  end
end

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

Особенности:

  • Может вызывать private/protected методы
  • Принимает строку или символ
  • Первый аргумент — имя метода, остальные — аргументы метода

2. Безопасная альтернатива public_send

user.public_send(:greet, "Bob")  # работает только с public методами
user.public_send(:initialize, "Eve")  # => NoMethodError (private method)

3. Метод method + call

Получение объекта Method и его последующий вызов:

greeter = user.method(:greet)
greeter.call("Charlie")  # => "Hello, Charlie!"

Преимущества:

  • Можно передавать как объект
  • Поддерживает to_proc
  • Может быть мемоизирован

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

1. Динамическое определение методов

class DynamicCalculator
  OPERATIONS = %i[add subtract multiply divide]

  OPERATIONS.each do |op|
    define_method(op) do |a, b|
      a.send(op, b)
    end
  end
end

calc = DynamicCalculator.new
calc.add(5, 3)  # => 8

2. Перехват вызовов через method_missing

class SmartHash
  def initialize(hash)
    @hash = hash
  end

  def method_missing(name, *)
    @hash[name.to_s] || @hash[name] || super
  end
end

data = SmartHash.new("name" => "John", age: 30)
data.name  # => "John"
data.age   # => 30

3. Комбинирование с блоками

def dynamic_with_block(method_name, &block)
  define_method(method_name) do |*args|
    block.call(*args)
  end
end

dynamic_with_block(:say_hi) { |name| "Hi, #{name}" }
say_hi("Dave")  # => "Hi, Dave"

Внутренняя механика вызова методов

  1. Поиск метода:

    • Ruby ищет метод в:
      1. Eigenclass объекта
      2. Классе объекта
      3. Модулях (в обратном порядке включения)
      4. Суперклассах
  2. Кэширование методов:

    • При первом вызове метод кэшируется
    • send использует тот же механизм кэширования
  3. Выполнение:

    • Создается кадр стека (frame)
    • Локальные переменные инициализируются
    • Выполняется код метода

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

Варианты вызовов по скорости (от быстрого к медленному):

  1. Обычный вызов (obj.method)
  2. send с существующим методом
  3. public_send
  4. method_missing
  5. method(...).call
require 'benchmark'

Benchmark.bm do |x|
  x.report("direct") { 1_000_000.times { "test".upcase } }
  x.report("send")   { 1_000_000.times { "test".send(:upcase) } }
  x.report("public_send") { 1_000_000.times { "test".public_send(:upcase) } }
end

Безопасность

  1. Проблемы:

    • Инъекция кода через параметры
    • Вызов неожиданных методов
  2. Защита:

    • Всегда проверяйте входные данные
    • Используйте whitelist для допустимых методов
    • Предпочитайте public_send вместо send
# Безопасный подход
SAFE_METHODS = %w[upcase downcase capitalize]

def safe_dynamic_call(str, method_name)
  if SAFE_METHODS.include?(method_name)
    str.send(method_name)
  else
    raise "Unsafe method attempted"
  end
end

Практические применения

  1. Адаптеры API:
class ApiAdapter
  def call(endpoint, params = {})
    method = "call_#{endpoint}"
    respond_to?(method) ? send(method, params) : call_default(params)
  end
end
  1. Делегирование:
class Delegator
  def initialize(target)
    @target = target
  end

  def method_missing(name, *args, &block)
    @target.respond_to?(name) ? @target.send(name, *args, &block) : super
  end
end
  1. DSL:
class Configuration
  def self.define_setters(*attrs)
    attrs.each do |attr|
      define_method("#{attr}=") { |value| instance_variable_set("@#{attr}", value) }
    end
  end
end

Резюмируем

Динамический вызов методов в Ruby:

  • Основан на методах send, public_send и method_missing
  • Позволяет создавать гибкие и адаптивные интерфейсы
  • Широко используется в метапрограммировании и DSL
  • Требует осторожности с безопасностью и производительностью

Ключевые правила:

  1. Используйте public_send вместо send когда возможно
  2. Проверяйте входные данные для динамических вызовов
  3. Кэшируйте объекты Method для повторных вызовов
  4. Документируйте неочевидные динамические поведения
  5. Рассмотрите альтернативы (шаблон Стратегия) когда динамика избыточна

Динамические методы — мощный инструмент, который делает Ruby уникальным, но требует ответственного использования.