Метапрограммирование – довольно интересный для меня вид программирования, так как он часто используется в
Ruby, в языке, на котором я активно пишу уже несколько лет. До этого момента я немного сторонился этой темы, знал о нём очень поверхностно. Но думаю, что пришло время разобраться в магии
Ruby и
Ruby on Rails.
Что такое метапрограммирование и почему это очень мощная штука?
Под метапрограммированием понимается написание кода, который манипулирует языковыми конструкциями. Иначе говоря, мы пишем код, который пишет код.
Мы можем написать код на языке
Java, используя аннотации, и после этого воспользоваться генератором кода для получения конфигурационных файлов xml. В широком смысле это ведь и есть метапрограммирование, но не совсем то, о котором я писал выше. Назовем этот вид метапрограммирования
статическим, тогда как метапрограммирования на
Ruby динамическим, так как код на
Ruby манипулирует языковыми конструкциями в
рантайме.
Пример:
Предположим, от нас требуется написать код, позволяющий осуществлять взаимодействие с бд.
class Entity
attr_reader :table, :id
def initialize(table, id)
@table = table
@id = id
Database.sql "INSERT INTO #{
@table} (id) VALUES (#{
@id})"
end
def set(col, val)
Database.sql "UPDATE #{
@table} SET #{col}='#{val}' WHERE id=#{
@id}"
end
def get(col)
Database.sql ("SELECT #{col} FROM #{
@table} WHERE id=#{
@id}")[0][0]
end
end
Когда мы создаем новый объект или взаимодействуем с его атрибутами, объект генерирует запрос к бд.
После этого мы спокойно можем наследоваться от класса
Entity.
class Movie < Entity
def initialize(id)
super "movies", id
end
def title
get "title"
end
def title=(value)
set "title", value
end
def director
get "director"
end
def director=(value)
set "director", value
end
end
Таким образом мы можем взаимодействовать с любой таблицей.
Для каждой таблицы мы должны:
1. Создать класс для взаимодействия с таблицей, наследоваться от Entity.
2. Вызвать конструктор родительского класса, передав название таблицы и id.
3. Для каждого столбца в таблице написать геттер и сеттер, использующий методы родительского класса.
Но вот это всё плохо,
очень плохо. Много дублирования кода!
Мы можем решить эту задачу, например, воспользовавшись гемом ActiveRecord (который крайне часто используются во фреймворке
Ruby on Rails ). Как взаимодействовать с бд теперь?
class Movie < ActiveRecord::Base
end
Этого достаточно для нашей задачи. Мы просто наследуемся от
ActiveRecord::Base. Нам не нужно указывать название таблицы (но мы можем это сделать, если хотим изменить обычное поведение). Нам не нужно писать геттеры, сеттеры и многое другое.
Что делает в данном случае ActiveRecord? Он просто ищет таблицу по названию класса (во множественном числе). Также он определяет геттеры и сеттеры на основе схемы бд.
Не будем пока что вдаваться в детали реализации, самое главное сейчас понять, что нам даёт динамическое метапрограммирование. Это правда очень мощная штука.