Ruby Language
Метапрограммирование
Поиск…
Вступление
Метапрограммирование можно описать двумя способами:
«Компьютерные программы, которые пишут или управляют другими программами (или самими собой) в качестве своих данных или выполняют часть работы во время компиляции, которая в противном случае была бы выполнена во время выполнения».
Проще говоря: Metaprogramming пишет код, который пишет код во время выполнения, чтобы сделать вашу жизнь проще .
Внедрение «с» с использованием оценки экземпляра
Многие языки оснащены with
утверждением , что позволяет программистам опускать приемник вызовов методов.
with
может легко эмулироваться в Ruby с помощью instance_eval
:
def with(object, &block)
object.instance_eval &block
end
Метод with
может использоваться для бесшовного выполнения методов на объектах:
hash = Hash.new
with hash do
store :key, :value
has_key? :key # => true
values # => [:value]
end
Определение методов динамически
С Ruby вы можете изменить структуру программы во время выполнения. Один из способов сделать это - это динамическое определение методов методом method_missing
.
Скажем, мы хотим проверить, превышает ли число число, отличное от другого, с синтаксисом 777.is_greater_than_123?
,
# open Numeric class
class Numeric
# override `method_missing`
def method_missing(method_name,*args)
# test if the method_name matches the syntax we want
if method_name.to_s.match /^is_greater_than_(\d+)\?$/
# capture the number in the method_name
the_other_number = $1.to_i
# return whether the number is greater than the other number or not
self > the_other_number
else
# if the method_name doesn't match what we want, let the previous definition of `method_missing` handle it
super
end
end
end
Одна важная вещь, которую следует помнить при использовании method_missing
, также следует переопределить respond_to?
метод:
class Numeric
def respond_to?(method_name, include_all = false)
method_name.to_s.match(/^is_greater_than_(\d+)\?$/) || super
end
end
Забыть сделать это приводит к непоследовательной ситуации, когда вы можете успешно позвонить 600.is_greater_than_123
, но 600.respond_to(:is_greater_than_123)
возвращает false.
Определение методов по экземплярам
В рубине вы можете добавлять методы к существующим экземплярам любого класса. Это позволяет добавлять поведение и экземпляр класса без изменения поведения остальных экземпляров этого класса.
class Example
def method1(foo)
puts foo
end
end
#defines method2 on object exp
exp = Example.new
exp.define_method(:method2) {puts "Method2"}
#with method parameters
exp.define_method(:method3) {|name| puts name}
метод send ()
send()
используется для передачи сообщения object
. send()
- это метод экземпляра класса Object
. Первым аргументом в send()
является сообщение, которое вы отправляете объекту, то есть имя метода. Это может быть string
или symbol
но символы предпочтительны. Затем аргументы, которые должны пройти в методе, будут остальными аргументами в send()
.
class Hello
def hello(*args)
puts 'Hello ' + args.join(' ')
end
end
h = Hello.new
h.send :hello, 'gentle', 'readers' #=> "Hello gentle readers"
# h.send(:hello, 'gentle', 'readers') #=> Here :hello is method and rest are the arguments to method.
Вот более описательный пример
class Account
attr_accessor :name, :email, :notes, :address
def assign_values(values)
values.each_key do |k, v|
# How send method would look a like
# self.name = value[k]
self.send("#{k}=", values[k])
end
end
end
user_info = {
name: 'Matt',
email: '[email protected]',
address: '132 random st.',
notes: "annoying customer"
}
account = Account.new
If attributes gets increase then we would messup the code
#--------- Bad way --------------
account.name = user_info[:name]
account.address = user_info[:address]
account.email = user_info[:email]
account.notes = user_info[:notes]
# --------- Meta Programing way --------------
account.assign_values(user_info) # With single line we can assign n number of attributes
puts account.inspect
Примечание: send()
сам по себе больше не рекомендуется. Используйте __send__()
который имеет право вызывать частные методы или (рекомендуется) public_send()