• Indexable: предназначен для коллекций, элементы которых имеют числовую позицию в строгом порядке и могут рассчитываться от 0
до размера коллекции. Ожидается, что класс предоставит метод size
и unsafe_fetch
. Indexable
включает Enumerable
и Iterable
и предоставляет все их методы, а также некоторые дополнения для работы с индексами.
Подробнее о каждом из этих модулей можно прочитать в официальной документации по адресу https://crystal-lang.org/docs.
Мы уже обсуждали использование модулей в качестве миксинов (mixins), когда их основной целью является включение в другой существующий класс. Вместо этого модуль может использоваться просто как пространство
имен или выступать в качестве держателя переменных и методов. Примером этого является модуль Base64 из стандартной библиотеки – он просто предоставляет некоторые служебные методы и не предназначен для включения в класс:
# Prints "Crystal Rocks!":
p Base64.decode_string("Q3J5c3RhbCBSb2NrcyE=")
В данном случае Base64 – это просто группа связанных методов, доступ к которым осуществляется непосредственно из модуля. Это общий шаблон, который помогает организовать методы и классы.
Подробнее о различных вариантах использования модулей будет рассказано позже в этой книге. Мы многое узнали о классах и объектах, но не все объекты ведут себя одинаково. Далее давайте разберемся, в чем разница между значениями и ссылками.
Значения и ссылки – использование структур
По умолчанию объекты Crystal размещаются в памяти и управляются сборщиком мусора. Это означает, что вам не нужно беспокоиться о том, где находится каждый объект в памяти и как долго он должен жить - среда выполнения позаботится о том, чтобы учесть, на какие объекты все еще ссылаются некоторые переменные, и освободит все остальные, автоматически освобождая ресурсы. Переменные не будут хранить объект как таковой - они будут хранить ссылку, указывающую на объект. Все это работает прозрачно, и беспокоиться об этом не нужно.
Вышесказанное справедливо для всех объектов, созданных из классов; типы этих объектов являются ссылочными типами. Но есть и другой тип объектов: типы значений.
На следующей диаграмме вы можете увидеть цепочку наследования некоторых типов. Те, которые являются ссылками, наследуются от ссылочного класса, в то время как те, которые являются значениями, наследуются от структуры Value
. Все они наследуются от специального базового типа Object
:
Рисунок 3.1 - Иерархия типов, показывающая, как ссылки связаны со значениями.
Ссылки управляются сборщиком мусора и хранятся в куче — специальной области памяти. Переменные указывают на них, и несколько переменных могут ссылаться на один и тот же объект. С другой стороны, объекты-значения живут в самих переменных и обычно имеют небольшой размер. Они создаются из структур.
Вы можете создавать свои собственные структуры. Они очень похожи на классы тем, что у них также есть переменные экземпляра и методы:
struct Address
property state : String, city : String
property line1 : String, line2 : String
property zip : String
def initialize(@state, @city, @line1, @line2, @zip)
end
end
Структуры и классы – это все типы объектов, и их можно использовать для ввода любой переменной, включая объединения типов. Например, давайте сохраним адрес внутри класса Person
:
class Person
property address : Address?
end
В данном случае переменная экземпляра @address
имеет тип Address?
это сокращение от Address | Nil
. Поскольку начального значения нет и эта переменная не назначается в методе initialize
, она начинается с nil
. Использование структуры является простым:
address = Address.new("CA", "Los Angeles", "Some fictitious line", "First house", "1234")
person1 = Person.new
person2 = Person.new
person1.address = address
address.zip = "ABCD"
person2.address = address
puts person1.address.try &.zip
puts person2.address.try &.zip
Мы начали этот пример с создания адреса и двух persons
– в общей сложности трех объектов: одного объекта-значения и двух объектов-ссылок. Затем мы присвоили адрес из локальной переменной address
переменной экземпляра @address
для person1
. Поскольку адрес является значением, эта операция копирует данные. Мы изменяем его и присваиваем @address
person2
. Обратите внимание, что изменение не влияет на person1
– значения всегда копируются. Наконец, мы показываем почтовый индекс в каждом адресе. Нам нужно использовать метод try
для доступа к свойству zip
только в том случае, если на данный момент значение union
не равно nil
, поскольку компилятор не может определить это самостоятельно.