litbaza книги онлайнРазная литератураCrystal Programming. Введение на основе проекта в создание эффективных, безопасных и читаемых веб-приложений и приложений CLI - Джордж Дитрих

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 16 17 18 19 20 21 22 23 24 ... 75
Перейти на страницу:
class="code">class Circle

    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), когда их основной целью является включение в другой существующий класс. Вместо этого модуль может использоваться просто как пространство

имен или выступать

1 ... 16 17 18 19 20 21 22 23 24 ... 75
Перейти на страницу:

Комментарии
Минимальная длина комментария - 20 знаков. Уважайте себя и других!
Комментариев еще нет. Хотите быть первым?