Шрифт:
Интервал:
Закладка:
def area : Number
Math::PI * @radius ** 2
end
end
class Rectangle
def area : Number
@width * @height
end
end
Определяя метод абстрактной области в родительском классе, мы гарантируем, что все подклассы должны будут определять его, используя одну и ту же сигнатуру (без аргументов, возвращая какое-либо число). Например, если у нас есть список фигур, мы можем быть уверены, что сможем вычислить площадь каждой из них.
Абстрактный класс не ограничивается абстрактными методами - он также может определять обычные методы и переменные экземпляра.
Переменные класса и методы класса
Объекты являются экземплярами определенного класса и хранят значения переменных его экземпляра. Хотя имена и типы переменных одинаковы, каждый экземпляр (каждый объект) может иметь разные значения для них. Если тип переменной экземпляра является объединением нескольких типов, то разные объекты могут хранить в себе значения разных типов. Класс описывает каркас, в то время как объекты являются живыми объектами.
Но классы тоже являются объектами! Разве у них не должны быть переменные экземпляра и методы? Да, конечно.
Когда вы создаете класс, вы можете определить переменные класса и методы класса. Они находятся в самом классе, а не в каком-либо конкретном объекте. Переменные класса обозначаются префиксом @@, точно так же, как переменные экземпляра имеют префикс @. Давайте посмотрим на это на практике:
class Person
@@next_id = 1
@id : Int32
def initialize(@name : String)
@id = @@next_id
@@next_id += 1
end
end
Здесь мы определили переменную класса с именем @@next_id. Она существует сразу для всей программы. У нас также есть переменные экземпляра @name и @id, которые существуют для каждого объекта Person:
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. Модуль добавит множество полезных методов для преобразования этого итератора.
• Indexable: предназначен для коллекций, элементы которых имеют числовую позицию в строгом порядке и могут рассчитываться от 0 до размера коллекции. Ожидается, что класс предоставит метод size и unsafe_fetch. Indexable включает Enumerable и Iterable и предоставляет все их методы, а также некоторые дополнения для работы с индексами.
Подробнее о каждом из этих модулей можно прочитать в официальной документации по адресу https://crystal-lang.org/docs.
Мы уже обсуждали использование модулей в качестве миксинов (mixins), когда их основной целью является включение в другой существующий класс. Вместо этого модуль может использоваться просто как пространство
имен или выступать