При написании своих классов желательно, чтобы семантика типичных операций была такой же, как у встроенных в Ruby классов. Например, если объекты класса можно упорядочивать, то имеет смысл реализовать метод
<=>
и подмешать модуль
Comparable
. Тогда к объектам вашего класса будут применимы все обычные операции сравнения.
Однако картина перестает быть такой однозначной, когда дело доходит до проверки объектов на равенство. В Ruby объекты реализуют пять разных методов
для этой операции. И в ваших классах придется реализовать хотя бы некоторые из них, поэтому рассмотрим этот вопрос подробнее.
Самым главным является метод
equal?
(унаследованный от класса
Object
); он возвращает
true
, если вызывающий объект и параметр имеют один и тот же идентификатор объекта. Это фундаментальный аспект семантики объектов, поэтому переопределять его не следует.
Самым распространенным способом проверки на равенство является старый добрый оператор
==
, который сравнивает значения вызывающего объекта и аргумента. Наверно, интуитивно это наиболее очевидный способ.
Следующим в шкале абстракции стоит метод
eql?
— тоже часть класса
Object
. (На самом деле метод
eql?
реализован в модуле
Kernel
, который подмешивается в
Object
.) Как и оператор
==
, этот метод сравнивает значения вызывающего объекта и аргумента, но несколько более строго. Например, разные числовые объекты при сравнении с помощью
==
приводятся к общему типу, но метод
eql?
никогда не считает объекты разных типов равными.
flag1 = (1 == 1.0) # true
flag2 = (1.eql?(1.0)) # false
Метод
eql?
существует по одной-единственной причине: для сравнения значений ключей хэширования. Если вы хотите переопределить стандартное поведение Ruby при использовании объектов в качестве ключей хэша, то переопределите методы
eql?
и
hash
.
Любой объект реализует еще два метода сравнения. Метод
===
применяется для сравнения проверяемого значения в предложении
case
с каждым селектором:
selector===target
. Хотя правило на первый взгляд кажется сложным, на практике оно делает предложения
case
в Ruby интуитивно очевидными. Например, можно выполнить ветвление по классу объекта:
case an_object
when String
puts "Это строка."
when Numeric
puts "Это число."
else
puts "Это что-то совсем другое."
end
Эта конструкция работает, потому что в классе
Module
реализован метод
===
, проверяющий, принадлежит ли параметр тому же классу, что вызывающий объект (или одному из его предков). Поэтому, если
an_object
— это строка «cat», выражение
string === an_object
окажется истинным и будет выбрана первая ветвь в предложении
case
.
Наконец, в Ruby реализован оператор сопоставления с образцом
=~
.
Традиционно он применяется для сопоставления строки с регулярным выражением. Но если вы найдете ему применение в других классах, то можете переопределить.
У операторов
==
и
=~
есть противоположные формы:
!=
и
!~
соответственно. Внутри они реализованы путем обращения значения основной формы. Это означает, что если, например, вы реализовали метод
==
, то метод
!=
получаете задаром.
11.1.8. Управление доступом к методам
В Ruby объект определяется, прежде всего, своим интерфейсом: теми методами, которые он раскрывает внешнему миру. Но при написании класса часто возникает необходимость во вспомогательных методах, вызывать которые извне класса опасно. Тут-то и приходит на помощь метод
private
класса
Module
.
Использовать его можно двумя способами. Если в теле класса или модуля вы вызовете
private
без параметров, то все последующие методы будут закрытыми в данном классе или модуле. Если же вы передадите ему список имен методов (в виде символов), то эти и только эти методы станут закрытыми. В листинге 11.5 показаны оба варианта.
Листинг 11.5. Закрытые методы
class Bank
def open_safe
# ...
end
def close_safe
# ...
end
private :open_safe, :close_safe
def make_withdrawal(amount)
if access_allowed
open_safe
get_cash(amount)
close_safe
end
end
# Остальные методы закрытые.
private
def get_cash
# ...
end
def access_allowed
# ...
end
end
Поскольку методы из семейства
attr
просто определяют методы, метод
private
определяет и видимость атрибутов.
Реализация метода
private
может показаться странной, но на самом деле она весьма хитроумна. К закрытым методам нельзя обратиться, указав вызывающий объект; они вызываются только от имени неявно подразумеваемого объекта
self
. То есть вызвать закрытый метод из другого объекта не удастся: просто не существует способа указать объект, от имени которого данный метод вызывается. Заодно это означает, что закрытые методы доступны подклассам того класса, в котором определены, но опять же в рамках одного объекта.