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

Каждый класс предоставляет некоторые методы объектам, которые являются его экземплярами. Например, все экземпляры класса String имеют метод size, который возвращает количество символов строки в виде объекта типа Int32. Аналогично, объекты типа Int32 имеют метод с именем +, который принимает другое число в качестве единственного аргумента и возвращает его сумму, как показано в следующем примере:

p "Crystal".size + 4 # => 11

Это то же самое, что и более явная форма:

p("Crystal".size().+(4)) # => 11

Это показывает, что все распространенные операторы и свойства — это просто вызовы методов.

Некоторые классы не имеют буквального представления, и объекты необходимо создавать непосредственно с использованием имени класса. Ниже приведен пример:

file = File.new("some_file.txt")

puts file.gets_to_end

file.close

Здесь file — это объект типа File, показывающий, как можно открыть файл, прочитать все его содержимое, а затем закрыть его. Новый метод вызывается в File для создания нового экземпляра класса. Этот метод получает строку в качестве аргумента и возвращает новый объект File, открывая указанный файл. Отсюда внутренняя реализация этого файла в памяти скрыта и взаимодействовать с ним можно только вызовом других методов. get_to_end затем используется для получения содержимого файла в виде строки, а метод close используется для закрытия файла и освобождения некоторых ресурсов.

Предыдущий пример можно упростить, используя вариант блока, который автоматически закрывает файл после его использования:

File.openCsome_file.txt") do |file|

    puts file.gets_to_end

end

В предыдущем фрагменте методу open передается блок, который получает в качестве аргумента файл (тот же, который возвращает new). Блок выполняется, а затем файл закрывается.

Возможно, вы заметили, что так же, как этот код вызывает метод gets_to_end объекта file, он также вызывает метод open класса File. Ранее вы узнали, что методы — это то, как мы общаемся с объектами, так почему же они используются и для взаимодействия с классом? Это очень важная деталь, о которой следует помнить: в Crystal все является объектами, даже классы. Все классы являются объектами типа Class, и их можно присваивать переменным точно так же, как простые значения:

p 23.class    # => Int32

p Int32.class # => Class

num = 10

type = Int32

p num.class == type # => true

p File.new("some_file.txt") # => #<File:some_file.txt>

file_class = File

p file_class.newCsome_file.txt") # => #<File:some_file.txt>

Теперь вы знаете, что примитивные значения — это объекты, экземпляры более сложных типов из классов стандартной библиотеки — это объекты, и что сами классы тоже являются объектами. Каждый объект имеет внутреннее состояние и раскрывает методы мышления поведения. Переменные используются для хранения этих объектов.

Хотя Crystal поставляется со многими полезными классами, и вы можете установить больше из внешних зависимостей, вы можете создавать свои собственные классы для всего, что вам нужно. Мы рассмотрим это в следующем разделе.

Создание собственных классов

Классы описывают поведение объектов. Приятно узнать, что стандартные типы, поставляемые с Crystal, по большей части представляют собой обычные классы, которые вы могли бы реализовать самостоятельно. Кроме того, вашему приложению понадобятся еще несколько специализированных классов, поэтому давайте их создадим.

Новые классы создаются с помощью ключевого слова class, за которым следует имя, а затем определение класса. Следующий минимальный пример:

class Person

end

person1 = Person.new

person2 = Person.new

В этом примере создается новый класс с именем Person, а затем два экземпляра этого класса — два объекта. Этот класс пуст — он не определяет никаких методов или данных, но классы Crystal по умолчанию имеют некоторую функциональность:

p person1   # You can display any object and inspect it

p person1.to_s # Any object can be transformed into a String

p person1 == person2  # false. By default, compares by reference.

p person1.same?(person2) # Also false, same as above.

p person1.nil?  # false, person1 isn't nil.

p person1.is_a?(Person) # true, person1 is an instance of Person.

Внутри класса вы можете определять методы так же, как и методы верхнего уровня. Один из таких методов особенный: метод initialize. Он вызывается всякий раз, когда создается новый объект, чтобы инициализировать его в исходное состояние. Данные, хранящиеся внутри объекта, хранятся в переменных экземпляра; они подобны локальным переменным, но они используются всеми методами класса и начинаются с символа @. Вот более полный класс Person: