first = Person.new("Adam") # This will have @id = 1
second = Person.new("Jess") # And this will have @id = 2
# @@next_id inside Person is now 3.
Имейте в виду, что эти переменные класса действуют как глобальные переменные, и их значения являются общими для всей программы. Хотя это полезно для некоторых глобальных состояний, это также не обеспечивает потокобезопасность в программах с включенным параллелизмом, поскольку могут возникнуть условия гонки. Предыдущий пример не является потокобезопасным, если экземпляры Person
создаются из разных потоков. Crystal по умолчанию не является многопоточным.
Подобно переменным класса, методы класса можно определить в самом классе, добавив к его имени префикс self
. Посмотри:
class Person
def self.reset_next_id
@@next_id = 1
end
end
Теперь вы можете вызвать Person.reset_next_id
для выполнения этого действия, работая напрямую с классом. Отсюда становится ясно, что классы действительно являются объектами, поскольку у них есть данные и методы. Все это работает, как и ожидалось, и с наследованием подклассов.
Поскольку метод класса вызывается для класса, а не для экземпляра класса, в игре нет никакого объекта, а ключевое слово self
относится к самому классу. Вы не можете получить доступ к переменным экземпляра или вызвать методы экземпляра, не обращаясь к какому-либо объекту.
Подобно переменным экземпляра, существуют вспомогательные макросы, помогающие предоставлять переменные класса с помощью методов класса, то есть class_getter
, class_setter
и class_property
:
class Person
class_property next_id
end
Теперь можно сделать Person.next_id = 3
или x = Person.next_id
.
Работа с модулями
Модули, как и абстрактные классы, не представляют собой конкретные классы, из которых можно создавать объекты. Вместо этого модули — это фрагменты класса реализации, которые можно включить в класс при его определении. Модули могут определять переменные экземпляра, методы, переменные класса, методы класса и абстрактные методы, все из которых внедряются в класс, который их включает.
Давайте рассмотрим пример модуля, который определяет метод Say_name
на основе некоторого существующего метода имени:
module WithSayName
abstract def name : String
def say_name
puts "My name is #{name}"
end
end
Это можно использовать с вашим классом Person
:
class Person
include WithSayName
property name : String
def initialize(@name : String)
end
end
Здесь метод имени, ожидаемый WithSayName
, создается макросом свойства. Теперь мы можем создать новый экземпляр Person
и вызвать для него Say_name
.
Модули можно использовать для ограничений типа и типа переменных. Когда это будет сделано, он укажет любой класс, включающий этот модуль. Учитывая ранее определенный код, мы можем сделать следующее:
def show(thing : WithSayName)
thing.say_name
end
show Person.new("Jim")
Как обычно, ограничения типов не являются обязательными, но они могут помочь улучшить читаемость и документацию.
Модули часто используются для той же цели, что и интерфейсы других языков, где определен общий набор характеристик и один и тот же модуль реализуется множеством разных классов. Кроме того, один класс может включать в себя столько модулей, сколько необходимо.
Стандартная библиотека включает в себя несколько полезных модулей для указания характеристик некоторых классов:
Comparable: реализует все операторы сравнения при условии, что вы правильно реализовали оператор <=>
. Классы, представляющие значения в естественном порядке, которые можно сортировать внутри контейнера, обычно включают этот модуль.
• Enumerable: используется для коллекций, элементы которых можно перечислять один за другим. Класс должен реализовать each
метод. передавая каждый элемент в блок. Этот модуль, в свою очередь, реализует несколько вспомогательных методов для управления коллекцией.
• Iterable: это означает, что можно лениво перебирать включающую коллекцию. Класс должен реализовать each
метод без получения блока и вернуть экземпляр Iterator
. Модуль добавит множество полезных методов для преобразования этого итератора.