def IntelligentLife.home_planet=(x)
@@home_planet = x
end
#...
end
class Terran < IntelligentLife
@@home_planet = 'Earth'
#...
end
class Martian < IntelligentLife
@@home_planet = 'Mars'
#...
end
Но это работать не будет. Вызов Terran.home_planet
напечатает не 'Earth'
, а 'Mars'
! Почему так? Дело в том, что переменные класса — на практике не вполне переменные класса; они принадлежат не одному классу, а всей иерархии наследования. Переменная класса не копируется из родительского класса, а разделяется родителем (и, стало быть, со всеми братьями).
Можно было бы вынести определение переменной класса в базовый класс, но тогда перестали бы работать определенные нами методы класса! Можно было исправить и это, перенеся определения в дочерние классы, однако тем самым губится первоначальная идея, ведь таким образом объявляются отдельные классы без какой бы то ни было «параметризации».
Мы предлагаем другое решение. Отложим вычисление переменной класса до момента выполнения, воспользовавшись методом class_eval
. Полное решение приведено в листинге 11.13.
class IntelligentLife
def IntelligentLife.home_planet
class_eval('@@home_planet')
end
def IntelligentLife.home_planet=(x)
class_eval('@@home_planet = #{x}')
end
# ...
end
class Terran < IntelligentLife
@@home_planet = 'Earth'
# ...
end
class Martian < IntelligentLife
@@home_planet = 'Mars'
# ...
end
puts Terran.home_planet # Earth
puts Martian.home_planet # Mars
Не стоит и говорить, что механизм наследования здесь по-прежнему работает. Все методы и переменные экземпляра, определенные в классе IntelligentLife
, наследуются классами Terran
и Martian
.
В листинге 11.14 предложено, наверное, наилучшее решение. В нем используются только переменные экземпляра, а от переменных класса мы вообще отказались.
class IntelligentLife
class << self
attr_accessor :home_planet
end
# ...
end
class Terran < IntelligentLife
self.home_planet = 'Earth'
#...
end
class Martian < IntelligentLife
self.home_planet = 'Mars'
#...
end
puts Terran.home_planet # Earth
puts Martian.home_planet # Mars
Здесь мы открываем синглетный класс и определяем метод доступа home_planet
. В двух подклассах определяются собственные методы доступа и устанавливается переменная. Теперь методы доступа работают строго в своих классах.
В качестве небольшого усовершенствования добавим еще вызов метода private
в синглетный класс:
private :home_planet=
Сделав метод установки закрытым, мы запретили изменять значение вне иерархии данного класса. Как всегда, private
реализует «рекомендательную» защиту, которая легко обходится. Но объявление метода закрытым по крайней мере говорит, что мы не хотели, чтобы метод вызывался вне определенного контекста.
Есть и другие способы решения этой задачи. Проявите воображение.
11.2.5. Использование продолжений для реализации генератора
Одно из самых трудных для понимания средств Ruby —