Книга: Программирование на языке Ruby

11.1.13. Трансформация или преобразование объектов

11.1.13. Трансформация или преобразование объектов

Иногда объект имеет нужный вид в нужное время, а иногда хочется преобразовать его во что-то другое или сделать вид, что он является чем-то, чем на самом деле не является. Всем известный пример — метод to_s.

Каждый объект можно тем или иным способом представить в виде строки. Но не каждый объект может успешно «прикинуться» строкой. Именно в этом и состоит различие между методами to_s и to_str. Рассмотрим этот вопрос подробнее.

При использовании метода puts или интерполяции в строку (в контексте #{...}) ожидается, что в качестве параметра будет передан объект string. Если это не так, объект просят преобразовать себя в string, посылая ему сообщение to_s. Именно здесь вы можете определить, как объект следует отобразить; просто реализуйте метод to_s в своем классе так, чтобы он возвращал подходящую строку.

class Pet
 def initialize(name)
  @name = name
 end
 # ...
 def to_s
  "Pet: #@name"
 end
end

Другие методы (например, оператор конкатенации строк +) не так требовательны, они ожидают получить нечто достаточно близкое к объекту string. В этом случае Мац решил, что интерпретатор не будет вызывать метод to_s для преобразования нестроковых аргументов, поскольку это могло бы привести к большому числу ошибок. Вместо этого вызывается более строгий метод to_str. Из всех встроенных классов только String и Exception реализуют to_str, и лишь String, Regexp и Marshal вызывают его. Увидев сообщение TypeError: Failed to convert xyz into string, можно смело сказать, что интерпретатор пытался вызвать to_str и потерпел неудачу.

Вы можете реализовать метод to_str и самостоятельно, например, если хотите, чтобы строку можно было конкатенировать с числом:

class Numeric
 def to_str
  to_s
 end
end
label = "Число " + 9 # "Число 9"

Аналогично обстоит дело и в отношении массивов. Для преобразования объекта в массив служит метод to_a, а метод to_ary вызывается, когда ожидается массив и ничего другого, например в случае множественного присваивания. Допустим, есть предложение такого вида:

а, b, с = x

Если x — массив из трех элементов, оно будет работать ожидаемым образом. Но если это не массив, интерпретатор попытается вызвать метод to_ary для преобразования в массив. В принципе это может быть даже синглетный метод (принадлежащий конкретному объекту). На само преобразование не налагается никаких ограничений, ниже приведен пример (нереалистичный), когда строка преобразуется в массив строк:

class String
 def to_ary
  return self.split("")
 end
end
str = "UFO"
a, b, с = str # ["U", "F", "O"]

Метод inspect реализует другое соглашение. Отладчики, утилиты типа irb и метод отладочной печати p вызывают inspect, чтобы преобразовать объект к виду, пригодному для вывода на печать. Если вы хотите, чтобы во время отладки объект раскрывал свое внутреннее устройство, переопределите inspect.

Есть и еще одна ситуация, когда желательно выполнять такие преобразования «за кулисами». Пользователь языка ожидает, что Fixnum можно прибавить к Float, а комплексное число Complex разделить на рациональное. Но для проектировщика языка это проблема. Если метод + класса Fixnum получает аргумент типа Float, то что он должен с ним делать? Он знает лишь, как складывать значения типа Fixnum. Для решения проблемы в Ruby реализован механизм приведения типов coerce.

Когда оператор + (к примеру) получает аргумент, которого не понимает, он пытается привести вызывающий объект и аргумент к совместимым типам, а затем значения этих типов сложить. Общий принцип использования метода coerce прямолинеен:

class MyNumberSystem
 def +(other)
  if other.kind_of?(MyNumberSystem)
   result = some_calculation_between_self_and_other
   MyNumberSystem.new(result)
  else
   n1, n2 = other.coerce(self)
   n1 + n2
  end
 end
end

Метод coerce возвращает массив из двух элементов: аргумент и вызывающий объект, приведенные к совместимым типам.

В примере выше мы полагались на то, что класс аргумента умеет как-то выполнять приведение. Будь мы законопослушными гражданами, реализовали бы приведение и в собственном классе, чтобы он мог работать с числами других видов. Для этого нужно знать, с какими типами мы можем работать напрямую, и приводить объект к одному из этих типов, когда возникает необходимость. Если мы сами не знаем, как это сделать, следует спросить у родителя:

def coerce(other)
 if other.kind_of?(Float)
  return other, self.to_f
 elsif other.kind_of?(Integer)
  return other, self.to_i
 else
  super
 end
end

Конечно, чтобы этот пример работал, наш объект должен реализовывать методы to_i и to_f.

Метод coerce можно использовать для реализации автоматического преобразования строк в числа, как это делается в языке Perl:

class String
 def coerce(n)
  if self['.']
   [n, Float(self)]
  else
   [n, Integer(self)]
  end
 end
end
x = 1 + "23" # 24
y = 23 * "1.23" # 29.29

Впрочем, поступать так необязательно. Однако мы настоятельно рекомендуем реализовывать метод coerce при разработке разного рода числовых классов.

Оглавление книги


Генерация: 1.448. Запросов К БД/Cache: 3 / 1
поделиться
Вверх Вниз