Каждый класс предоставляет некоторые методы объектам, которые являются его экземплярами. Например, все экземпляры класса 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
: