11.1.14. Классы, содержащие только данные (Struct)
Иногда нужно просто сгруппировать взаимосвязанные данные, не определяя никакие специфические методы обработки. Можно для этого создать класс:
class Address
attr_accessor :street, :city, :state
def initialize(street1, city, state)
@street, @city, @state = street, city, state
end
end
books = Address.new('411 Elm St', 'Dallas', 'TX')
Такое решение годится, но каждый раз прибегать к нему утомительно; к тому же здесь слишком много повторов. Тут-то и приходит на помощь встроенный класс Struct
. Если вспомогательные методы типа attr_accessor
определяют методы доступа к атрибутам, то Struct
определяет целый класс, который может содержать только атрибуты. Такие классы называются структурными шаблонами.
Address = Struct.new('Address', :street, :city, :state)
books = Address.new('411 Elm St', 'Dallas', 'TX')
Зачем передавать первым параметром конструктора имя создаваемой структуры и присваивать результат константе (в данном случае Address
)?
При вызове Struct.new
для создания нового структурного шаблона на самом деле создается новый класс внутри самого класса Struct
. Этому классу присваивается имя, переданное первым параметром, а остальные параметры становятся именами его атрибутов. При желании к вновь созданному классу можно было бы получить доступ, указав пространство имен Struct
:
Struct.new('Address', :street, :city, :state)
books = Struct::Address.new('411 Elm St', 'Dallas', 'TX')
Создав структурный шаблон, вы вызываете его метод new для создания новых экземпляров данной конкретной структуры. Необязательно присваивать значения всем атрибутам в конструкторе. Опущенные атрибуты получат значение nil
. После того как структура создана, к ее атрибутам можно обращаться с помощью обычного синтаксиса или указывая их имена в скобках в качестве индекса, как будто структура - это объект класса Hash
. Более подробную информацию о классе Struct
можно найти в любом справочном руководстве (например, на сайте ruby.doc.org).
Кстати, не рекомендуем создавать структуру с именем Tms
, так как уже есть предопределенный класс Struct::Tms
.
11.1.15. Замораживание объектов
Иногда необходимо воспрепятствовать изменению объекта. Это позволяет сделать метод freeze
(определенный в классе Object
). По существу, он превращает объект в константу.
Попытка модифицировать замороженный объект приводит к исключению TypeError
. В листинге 11.8 приведено два примера.
str = 'Это тест. '
str.freeze
begin
str << ' He волнуйтесь.' # Попытка модифицировать.
rescue => err
puts '#{err.class} #{err}'
end
arr = [1, 2, 3]
arr.freeze
begin
arr << 4 # Попытка модифицировать.
rescue => err
puts '#{err.class} #{err}'
end
# Выводится:
# TypeError: can't modify frozen string
# TypeError: can't modify frozen array
Однако имейте в виду, что метод freeze
применяется к ссылке на объект, а не к переменной! Это означает, что любая операция, приводящая к созданию нового объекта, завершится успешно. Иногда это противоречит интуиции. В примере ниже мы ожидаем, что операция +=
не выполнится, но все работает нормально. Дело в том, что присваивание — не вызов метода. Эта операция воздействует на переменные, а не на объекты, поэтому новый объект создается беспрепятственно. Старый объект по-прежнему заморожен, но переменная ссылается уже не на него.
str = 'counter-'
str.freeze
str += 'intuitive' # 'counter-intuitive'
arr = [8, 6, 7]
arr.freeze
arr += [5, 3, 0, 9] # [8, 6, 7, 5, 3, 0, 9]
Почему так происходит? Предложение a += x
семантически эквивалентно a = a + x
. При вычислении выражения a + x
создается новый объект, который затем присваивается переменной a
! Все составные операторы присваивания работают подобным образом, равно как и другие методы. Всегда задавайте себе вопрос: «Что я делаю — создаю новый объект или модифицирую существующий?» И тогда поведение freeze
не станет для вас сюрпризом.
Существует метод frozen?
, который сообщает, заморожен ли данный объект.
hash = { 1 => 1, 2 => 4, 3 => 9 }
hash.freeze
arr = hash.to_a
puts hash.frozen? # true
puts arr.frozen? # false
hash2 = hash
puts hash2.frozen? # true
Как видите (на примере hash2
), замораживается именно объект, а не