Выбрать главу

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, поскольку компилятор не может определить это самостоятельно.