class Person
def initialize(name : String)
@name = name
@age = 0
end
def age_up
@age += 1
end
def name
@name
end
def name=(new_name)
@name = new_name
end
end
Здесь мы создали более реалистичный класс Person
с внутренним состоянием, состоящим из @name
, String
, @age
и Int32
. В классе есть несколько методов, которые взаимодействуют с этими данными, включая метод initialize
, который создаст нового человека — ребенка.
Теперь давайте воспользуемся этим классом:
jane = Person.new("Jane Doe")
p jane # => #<Person:0x7f97ae6f3ea0 @name="Jane Doe", # @age=0>
jane.name = "Mary"
5.times { jane.age_up }
p jane # => #<Person:0x7f97ae6f3ea0 @name="Mary", @age=5>
В этом примере создается экземпляр Person
путем передачи строки новому методу. Эта строка используется для инициализации объекта и в конечном итоге присваивается переменной экземпляра @name
. По умолчанию объекты можно проверять с помощью метода верхнего уровня p
, который показывает имя класса, адрес в памяти и значение переменных экземпляра. Следующая строка вызывает метод name=(new_name)
— он может делать что угодно, но для удобства он обновляет переменную @name
новым значением. Затем мы вызываем age_up
пять раз и снова проверяем объект. Здесь вы должны увидеть новое имя и возраст человека.
Обратите внимание, что в методе initialize
мы явно указываем тип аргумента имени вместо того, чтобы позволить компилятору определить его на основе использования. Здесь это необходимо, поскольку типы переменных экземпляра должны быть известны только из класса и не могут быть выведены из использования. Вот почему нельзя сказать, что Crystal имеет механизм вывода глобального типа.
Теперь давайте углубимся в то, как можно определять методы и переменные экземпляра.
Манипулирование данными с использованием переменных и методов экземпляра
Все данные внутри объекта хранятся в переменных экземпляра; их имена всегда начинаются с символа @
. Существует несколько способов определить переменную экземпляра для класса, но одно правило является фундаментальным: их тип должен быть известен. Тип может быть либо указан явно, либо синтаксически выведен компилятором.
Начальное значение переменной экземпляра может быть задано либо внутри метода initialize
, либо непосредственно в теле класса. В последнем случае он ведет себя так, как если бы переменная была инициализирована в начале метода initialize
. Если переменная экземпляра не назначена ни в одном методе initialize
, ей неявно присваивается значение nil
.
Тип переменной будет определяться из каждого присвоения ей в классе, из всех методов. Но имейте в виду, что их тип может зависеть только от литеральных значений или типизированных аргументов и больше ни от чего. Давайте посмотрим несколько примеров:
class Point
def initialize(@x : Int32, @y : Int32)
end
end
origin = Point.new(0, 0)
В этом первом случае класс Point
указывает, что его объекты имеют две целочисленные переменные экземпляра. Метод initialize
будет использовать свои аргументы, чтобы предоставить им начальное значение:
class Cat
@birthday = Time.local
def adopt(name : String)
@name = name
end
end
my_cat = Cat.new
my_cat.adopt("Tom")
Теперь у нас есть класс, описывающий кошку. У него нет метода initialize
, поэтому он ведет себя так, как если бы он был пустым. Переменная @birthday
назначается Time.local
. Это происходит внутри этого пустого метода initialize
при создании нового экземпляра объекта. Предполагается, что тип является экземпляром Time
, поскольку Time.local
вводится так, чтобы всегда возвращать его. Переменная @name
получает строковое значение из типизированного аргумента, но нигде не имеет начального значения, поэтому ее тип — String?
(это также можно представить как String | Nil
).
Обратите внимание, что выведение переменной экземпляра из аргумента работает только в том случае, если параметр указан явно, а переменной экземпляра присваивается непосредственно значение. Следующий пример недействителен: