Хэл Фултон - Программирование на языке Ruby
Класс Module содержит методы attr, attr_accessor, attr_reader и attr_writer. Ими можно пользоваться (передавая символы в качестве параметров) для автоматизации управления доступом к данным экземпляра. Например, все три метода getmyvar, setmyvar и myvar= можно заменить одной строкой в определении класса:
attr_accessor :myvar
При этом создается метод myvar, который возвращает значение @myvar, и метод myvar=, который позволяет изменить значение той же переменной. Методы attr_reader и attr_writer создают соответственно версии методов доступа к атрибуту для чтения и для изменения.
Внутри методов экземпляра, определенных в классе, можно при необходимости пользоваться переменной self. Это просто ссылка на объект, от имени которого вызван метод экземпляра.
Для управления видимостью методов класса можно пользоваться модификаторами private, protected и public. (Переменные экземпляра всегда закрыты, обращаться к ним извне класса можно только с помощью методов доступа.) Каждый модификатор принимает в качестве параметра символ, например :foo, а если он опущен, то действие модификатора распространяется на все последующие определения в классе. Пример:
class MyClass
def method1
# ...
end
def method2
# ...
end
def method3
# ...
end
private :method1
public
:method2
protected :method3
private
def my_method
# ...
end
def another_method
# ...
end
end
В этом классе метод method1 закрытый, method2 открытый, a method3 защищенный. Поскольку далее вызывается метод private без параметров, то методы my_method и another_method будут закрытыми.
Уровень доступа public не нуждается в объяснениях, он не налагает никаких ограничений ни на доступ к методу, ни на его видимость. Уровень private означает, что метод доступен исключительно внутри класса или его подклассов и может вызываться только в «функциональной форме» от имени self, причем вызывающий объект может указываться явно или подразумеваться неявно. Уровень protected означает, что метод вызывается только внутри класса, но, в отличие от закрытого метода, не обязательно от имени self.
По умолчанию все определенные в классе методы открыты. Исключение составляет лишь initialize. Методы, определенные на верхнем уровне программы, тоже по умолчанию открыты. Если они объявлены закрытыми, то могут вызываться только в функциональной форме (как, например, методы, определенные в классе Object).
Классы в Ruby сами являются объектами — экземплярами метакласса Class. Классы в этом языке всегда конкретны, абстрактных классов не существует. Однако теоретически можно реализовать и абстрактные классы, если вам это для чего-то понадобится.
Класс Object является корнем иерархии. Он предоставляет все методы, определенные во встроенном модуле Kernel.
Чтобы создать класс, наследующий другому классу, нужно поступить следующим образом:
class MyClass < OtherClass
# ...
end
Помимо использования встроенных методов, вполне естественно определить и собственные либо переопределить унаследованные. Если определяемый метод имеет то же имя, что и существующий, то старый метод замещается. Если новый метод должен обратиться к замещенному им «родительскому» методу (так бывает часто), можно воспользоваться ключевым словом super.
Перегрузка операторов, строго говоря, не является неотъемлемой особенностью ООП, но этот механизм знаком программистам на C++ и некоторых других языках. Поскольку большинство операторов в Ruby так или иначе являются методами, то не должен вызывать удивления тот факт, что их можно переопределять или определять в пользовательских классах. Переопределять семантику оператора в существующем классе редко имеет смысл, зато в новых классах определение операторов — обычное дело.
Можно создавать синонимы методов. Для этого внутри определения класса предоставляется такой синтаксис:
alias newname oldname
Число параметров будет таким же, как для старого имени, и вызываться метод-синоним будет точно так же. Обратите внимание на отсутствие запятой; alias — это не имя метода, а ключевое слово. Существует метод с именем alias_method, который ведет себя аналогично, но в случае его применения параметры должны разделяться запятыми, как и для любого другого метода.
1.3.5. Методы и атрибуты
Как мы уже видели, методы обычно используются в сочетании с простыми экземплярами классов и переменными, причем вызывающий объект отделяется от имени метода точкой (receiver.method). Если имя метода является знаком препинания, то точка опускается. У методов могут быть аргументы:
Time.mktime(2000, "Aug", 24, 16, 0)
Поскольку каждое выражение возвращает значение, то вызовы методов могут сцепляться:
3.succ.to_s
/(x.z).*?(x.z).*?/.match("x1z_1a3_x2z_1b3_").to_a[1..3]
3+2.succ
Отметим, что могут возникать проблемы, если выражение, являющееся результатом сцепления, имеет тип, который не поддерживает конкретный метод. Точнее, при определенных условиях некоторые методы возвращают nil, а вызов любого метода от имени такого объекта приведет к ошибке. (Конечно, nil — полноценный объект, но он не обладает теми же методами, что и, например, массив.)
Некоторым методам можно передавать блоки. Это верно для всех итераторов — как встроенных, так и определенных пользователем. Блок обычно заключается в операторные скобки do-end или в фигурные скобки. Но он не рассматривается так же, как предшествующие ему параметры, если таковые существуют. Вот пример вызова метода File.open:
my_array.each do |x|
some_action
end
File.open(filename) { |f| some_action }
Именованные параметры будут поддерживаться в последующих версиях Ruby, но на момент работы над этой книгой еще не поддерживались. В языке Python они называются ключевыми аргументами, сама идея восходит еще к языку Ada.
Методы могут принимать переменное число аргументов:
receiver.method(arg1, *more_args)
В данном случае вызванный метод трактует more_args как массив и обращается с ним, как с любым другим массивом. На самом деле звездочка в списке формальных параметров (перед последним или единственным параметром) может «свернуть» последовательность фактических параметров в массив:
def mymethod(a, b, *с)
print a, b
с.each do |x| print x end
end
mymethod(1,2,3,4,5,6,7)
# a=1, b=2, c=[3,4,5,6,7]
В Ruby есть возможность определять методы на уровне объекта (а не класса). Такие методы называются синглетными; они принадлежат одному-единственному объекту и не оказывают влияния ни на класс, ни на его суперклассы. Такая возможность может быть полезна, например, при разработке графических интерфейсов пользователя: чтобы определить действие кнопки, вы задаете синглетный метод для данной и только данной кнопки.
Вот пример определения синглетного метода для строкового объекта:
str = "Hello, world!"
str2 = "Goodbye!"
def str.spell
self.split(/./).join("-")
end
str.spell # "H-e-l-l-o-,- -w-o-r-l-d-!"
str2.spell # Ошибка!
Имейте в виду, что метод определяется для объекта, а не для переменной. Теоретически с помощью синглетных методов можно было бы создать систему объектов на базе прототипов. Это менее распространенная форма ООП без классов[5]. Основной структурный механизм в ней состоит в конструировании нового объекта путем использования существующего в качестве образца; новый объект ведет себя как старый за исключением тех особенностей, которые были переопределены. Тем самым можно строить системы на основе прототипов, а не наследования. Хотя у нас нет опыта в этой области, мы полагаем, что создание такой системы позволило бы полнее раскрыть возможности Ruby.
1.4. Динамические аспекты Ruby
Ruby — динамический язык в том смысле, что объекты и классы можно изменять во время выполнения. Ruby позволяет конструировать и интерпретировать фрагменты кода в ходе выполнения статически написанной программы. В нем есть хитроумный API отражения, с помощью которого программа может получать информацию о себе самой. Это позволяет сравнительно легко создавать отладчики, профилировщики и другие подобные инструменты, а также применять нетривиальные способы кодирования.