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), замораживается именно объект, а не
