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

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"), как если бы = была любая другая буква. Мы можем воспользоваться этим, чтобы написать метод установки: