class Person
def initialize(first_name, last_name)
@name = first_name + " " + last_name
end
end
person = Person.new("John", "Doe")
В этом примере переменная @name
создается путем объединения двух аргументов с пробелами между ними. Здесь тип этой переменной невозможно определить без более глубокого анализа типов двух параметров и результата вызова метода +
. Даже если бы аргументы были явно типизированы как String
, информации все равно было бы недостаточно, поскольку метод +
для строк может быть переопределен где-то в коде, чтобы возвращать какой-либо другой произвольный тип. В подобных случаях необходимо объявить тип переменной экземпляра:
class Person
@name : String
def initialize(first_name, last_name)
@name = first_name + " " + last_name
end
end
В качестве альтернативы можно использовать буквальную интерполяцию строки, поскольку она гарантированно всегда создает строку:
class Person
def initialize(first_name, last_name)
@name = "#{first_name} #{last_name}"
end
end
В любой ситуации допускается явное объявление типа переменной экземпляра, возможно, для ясности.
Вы можете задаться вопросом, почему компилятор не анализирует всю программу и каждый вызов метода, чтобы самостоятельно определить типы каждой переменной экземпляра, как он уже делает это для локальных переменных? Компилятор делал именно это в первые дни, но эта функция была удалена, поскольку этот анализ был слишком дорогим с точки зрения производительности и делал инкрементальную компиляцию невозможной в будущем. Существующие правила вывода переменных экземпляра в большинстве случаев успешны, и их редко приходится вводить.
Переменные экземпляра представляют частное состояние объекта, и ими следует манипулировать только с помощью методов внутри класса. Их можно раскрыть через геттеры и сеттеры. Доступ к переменным экземпляра можно получить извне с помощью синтаксиса obj.@ivar
, но это не рекомендуется.
Создание геттеров и сеттеров
В Crystal нет специальной концепции метода получения или установки свойств объекта; вместо этого они построены на основе функций, о которых мы уже узнали. Допустим, у нас есть человек, у которого есть переменная экземпляра имени:
class Person
def initialize(@name : String)
end
end
Мы уже можем создать нового человека и проверить его:
person = Person.new("Tony")
p person
Но было бы неплохо иметь возможность написать что-то вроде следующего, как если бы @name
был доступен:
puts "My name is #{person.name}"
person.name
— это просто вызов метода name объекта person
. Помните, что круглые скобки необязательны для вызовов методов. Мы можем пойти дальше и создать именно этот метод:
class Person
def name
@name
end
end
Теперь вызов person.name действителен, как если бы переменная экземпляра была доступна извне. В качестве дополнительного преимущества будущий рефакторинг может изменить внутреннюю структуру объекта и переопределить этот метод, не затрагивая пользователей. Это настолько распространено, что специально для этого существует служебный макрос:
class Person
getter name
end
Предыдущие два фрагмента ведут себя одинаково. Макрос-получатель создает метод, предоставляющий переменную экземпляра. Его также можно комбинировать с объявлением типа или начальным значением:
class Person
getter name : String
getter age = 0
getter height : Float64 = 1.65
end
Несколько геттеров могут быть созданы в одной строке:
class Person
getter name : String, age = 0, height : Float64 = 1.65
end
Для сеттеров логика очень похожа. Имена методов Crystal могут заканчиваться символом =
для обозначения установщика. Если у него один параметр, его можно вызвать с помощью удобного синтаксиса:
class Person
def name=(new_name)
puts "The new name is #{new_name}"
end
end
Этот метод name=
можно вызвать следующим образом:
person = Person.new("Tony")
person.name = "Alfred"
Последняя строка представляет собой просто вызов метода и не меняет значение переменной экземпляра @name
. Это то же самое, что написать person.name=("Alfred")
, как если бы =
была любая другая буква. Мы можем воспользоваться этим, чтобы написать метод установки: