Можно (но нецелесообразно) делать именно то, что вы просите.
Есть два разных элемента желаемого поведения. Первый хранит x
в доступном только для чтения значении , а второй защищает геттер от изменения в подклассах.
Значение только для чтения
В Ruby можно хранить значения только для чтения во время инициализации. Для этого мы используем поведение замыкания блоков Ruby.
class Foo
def initialize (x)
define_singleton_method(:x) { x }
end
end
Начальное значение x
теперь заблокировано внутри блока, который мы использовали для определения геттера #x
, и к нему нельзя получить доступ, кроме как путем вызова foo.x
, и его никогда нельзя изменить.
foo = Foo.new(2)
foo.x # => 2
foo.instance_variable_get(:@x) # => nil
Обратите внимание, что он не сохраняется как переменная экземпляра @x
, но все еще доступен через созданный нами метод получения с помощью define_singleton_method
.
Защита геттера
В Ruby практически любой метод любого класса может быть перезаписан во время выполнения. Есть способ предотвратить это, используя method_added
hook.
class Foo
def self.method_added (name)
raise(NameError, "cannot change x getter") if name == :x
end
end
class Bar < Foo
def x
20
end
end
# => NameError: cannot change x getter
Это очень сложный метод защиты геттера.
Требуется, чтобы мы добавляли каждый защищенный геттер в хук method_added
индивидуально, и даже тогда вам нужно будет добавить еще один уровень защиты method_added
в Foo
и его подклассы, чтобы кодер не перезаписывал method_added
сам метод.
Лучше смириться с тем фактом, что замена кода во время выполнения является фактом жизни при использовании Ruby.