Шрифт:
Интервал:
Закладка:
# 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, поскольку компилятор не может определить это самостоятельно.
Поэкспериментируйте с изменением адреса класса и повторным запуском предыдущего кода. На этот раз у обоих пользователей будет одинаковый почтовый индекс. Это происходит потому, что ссылки не копируются при присвоении, поэтому все переменные будут ссылаться на один и тот же объект address.
Значения структуры всегда копируются, когда вы присваиваете их из одной переменной в другую, когда вы передаете их в качестве аргументов при вызове метода или когда вы получаете их из возвращаемого значения при вызове метода. Это известно как семантика "по значению"; таким образом, рекомендуется , чтобы структуры были небольшими с точки зрения объема их памяти. Из этого правила есть интересное и полезное исключение: когда тело метода просто возвращает переменную экземпляра напрямую, копия удаляется, и к значению осуществляется прямой доступ. Давайте рассмотрим пример:
struct Location
property latitude = 0.0, longitude = 0.0
end
class Building
property gps = Location.new
end
building = Building.new
building.gps.latitude = 1.5
p store
В предыдущем примере мы создали структурный тип Location, который имеет два свойства, и класс Building, который имеет одно свойство. Макрос property gps сгенерирует метод с именем def gps; @gps; end для получателя - обратите внимание, что этот метод просто возвращает переменную экземпляра напрямую, что соответствует правилу исключения копирования. Если бы этот метод был каким-то другим, этот пример не сработал бы.
Строка building.gps.latitude = 1.5 вызывает метод gps и получает результат, затем вызывает параметр latitude=setter с значением 1.5 в качестве аргумента. Если бы возвращаемое значение gps было скопировано, то средство настройки работало бы с копией структуры и не влияло бы на значение, хранящееся в переменной building. Попробуйте поэкспериментировать с добавлением пользовательского определения для метода gps.
Теперь, когда вы знаете, как создавать как классы, так и структуры, мы сделаем шаг вперед и узнаем о дженериках и о том, как эта новая концепция может помочь вам создавать более гибкие типы.
Общие (Generic) классы
Общий класс (или структура) создается на основе одного или нескольких неизвестных типов, которые определяются только позже, когда вы создаете экземпляр указанного класса. Это звучит сложно, но вы уже использовали некоторые общие классы раньше. Array является наиболее распространенным: заметили ли вы, что нам всегда нужно указывать тип данных, которые содержит массив? Недостаточно сказать, что данная переменная является массивом — мы должны сказать, что это массив строк или