Главная Обратная связь

Дисциплины:






Кодирование во время выполнения



Мы уже упоминали директивы load и require. Важно понимать, что это не встроенные предложения и не управляющие конструкции; на самом деле это методы. Поэтому их можно вызывать, передавая переменные или выражения как параметры, в том числе условно. Сравните с директивой #include в языках С и C++, которая обрабатывается во время компиляции.

Код можно строить и интерпретировать по частям. В качестве несколько искусственного примера рассмотрим приведенный ниже метод calculate и вызывающий его код:

def calculate(op1, operator, op2)

string = op1.to_s + operator + op2.to_s

# Предполагается, что operator - строка; построим длинную

# строку, состоящую из оператора и операндов.

eval(string) # Вычисляем и возвращаем значение.

end

 

@alpha = 25

@beta = 12

 

puts calculate(2, "+",2) # Печатается 4

puts calculate(5, "*", "@alpha") # Печатается 125

puts calculate("@beta", "**", 3) # Печатается 1728

Вот та же идея, доведенная чуть ли не до абсурда: программа запрашивает у пользователя имя метода и одну строку кода. Затем этот метод определяется и вызывается:

puts "Имя метода: "

meth_name = gets

puts "Строка кода: "

code = gets

 

string = %[def #{meth_name}\n #{code}\n end] # Строим строку.

eval(string) # Определяем метод.

eval(meth_name) # Вызываем метод.

Зачастую необходимо написать программу, которая могла бы работать на разных платформах или при разных условиях, но при этом сохранить общий набор исходных текстов. Для этого в языке С применяются директивы #ifdef, но в Ruby все определения исполняются. Не существует такого понятия, как «этап компиляции»; все конструкции динамические, а не статические. Поэтому для принятия решения такого рода мы можем просто вычислить условие во время выполнения:

if platform == Windows

action1

elsif platform == Linux

action2

else

default_action

end

Конечно, за такое кодирование приходится расплачиваться некоторым снижением производительности, поскольку иногда условие вычисляется много раз. Но рассмотрим следующий пример, который делает практически то же самое, однако весь платформенно-зависимый код помещен в один метод, имя которого от платформы не зависит:

if platform == Windows

def my_action

action1

end

elsif platform == Linux

def my_action

action2

end

else

def my_action

default_action

end

end

Таким способом мы достигаем желаемого результата, но условие вычисляется только один раз. Когда программа вызовет метод my_action, он уже будет правильно определен.

Отражение



В языках Smalltalk, LISP и Java реализована (с разной степенью полноты) идея рефлексивного программирования — активная среда может опрашивать структуру объектов и расширять либо модифицировать их во время выполнения.

В языке Ruby имеется развитая поддержка отражения, но все же он не заходит так далеко, как Smalltalk, где даже управляющие конструкции являются объектами. В Ruby управляющие конструкции и блоки не представляют собой объекты. (Объект Proc можно использовать для того, чтобы представить блок в виде объекта, но управляющие конструкции объектами не бывают никогда.)

Для определения того, используется ли идентификатор с данным именем, служит ключевое слово defined? (обратите внимание на вопросительный знак в конце слова):

if defined? some_var

puts "some_var = #{some_var}"

else

puts "Переменная some_var неизвестна."

end

Аналогично метод respond_to? выясняет, может ли объект отвечать на вызов указанного метода (то есть определен ли данный метод для данного объекта). Метод respond_to? определен в классе Object.

В Ruby запрос информации о типе во время выполнения поддерживается очень полно. Тип или класс объекта можно определить, воспользовавшись методом type (из класса Object). Метод is_a?сообщает, принадлежит ли объект некоторому классу (включая и его суперклассы); синонимом служит имя kind_of?. Например:

puts "abc".class "" # Печатается String

puts 345.class # Печатается Fixnum

rover = Dog.new

 

print rover.class # Печатается Dog

 

if rover.is_a? Dog

puts "Конечно, является."

end

 

if rover.kind_of? Dog

puts "Да, все еще собака."

end

 

if rover.is_a? Animal

puts "Да, он к тому же и животное."

end

Можно получить полный список всех методов, которые можно вызвать для данного объекта. Для этого предназначен метод methods из класса Object. Имеются также его вариантыprivate_instance_methods, public_instance_methods и т.д.

Аналогично можно узнать, какие переменные класса или экземпляра ассоциированы с данным объектом. По самой природе ООП в перечни методов и переменных включаются те, что определены как в классе самого объекта, так и во всех его суперклассах. В классе Module имеется метод constants, позволяющий получить список всех констант, определенных в модуле.

В классе Module есть метод ancestors, возвращающий список модулей, включенных в данный модуль. В этот список входит и сам данный модуль, то есть список, возвращаемый вызовомMod.ancestors, содержит по крайней мере элемент Mod. В этот список входят не только родительские классы (отобранные в силу наследования), но и «родительские» модули (отобранные в силу включения).

В классе Object есть метод superclass, который возвращает суперкласс объекта или nil. Не имеет суперкласса лишь класс Object, и, значит, только для него может быть возвращен nil.

Модуль ObjectSpace применяется для получения доступа к любому «живому» объекту. Метод _idtoref преобразует идентификатор объекта в ссылку на него; можно считать, что это операция, обратная той, что выполняет двоеточие в начале имени. В модуле ObjectSpace есть также итератор each_object, который перебирает все существующие в данный момент объекты, включая и те, о которых иным образом узнать невозможно. (Напомним, что некоторые неизменяемые объекты небольшого размера, например принадлежащие классам Fixnum, NilClass, TrueClass и FalseClass, не хранятся в куче из соображений оптимизации.)





sdamzavas.net - 2019 год. Все права принадлежат их авторам! В случае нарушение авторского права, обращайтесь по форме обратной связи...