Категории
Самые читаемые
Лучшие книги » Компьютеры и Интернет » Программирование » Программирование на языке Ruby - Хэл Фултон

Программирование на языке Ruby - Хэл Фултон

Читать онлайн Программирование на языке Ruby - Хэл Фултон

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 81 82 83 84 85 86 87 88 89 ... 156
Перейти на страницу:

Когда оператор + (к примеру) получает аргумент, которого не понимает, он пытается привести вызывающий объект и аргумент к совместимым типам, а затем значения этих типов сложить. Общий принцип использования метода 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 при разработке разного рода числовых классов.

11.1.14. Классы, содержащие только данные (Struct)

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

class Address

 attr_accessor :street, :city, :state

 def initialize(street1, city, state)

  @street, @city, @state = street, city, state

 end

end

books = Address.new("411 Elm St", "Dallas", "TX")

Такое решение годится, но каждый раз прибегать к нему утомительно; к тому же здесь слишком много повторов. Тут-то и приходит на помощь встроенный класс Struct. Если вспомогательные методы типа attr_accessor определяют методы доступа к атрибутам, то Struct определяет целый класс, который может содержать только атрибуты. Такие классы называются структурными шаблонами.

Address = Struct.new("Address", :street, :city, :state)

books = Address.new("411 Elm St", "Dallas", "TX")

Зачем передавать первым параметром конструктора имя создаваемой структуры и присваивать результат константе (в данном случае Address)?

При вызове Struct.new для создания нового структурного шаблона на самом деле создается новый класс внутри самого класса Struct. Этому классу присваивается имя, переданное первым параметром, а остальные параметры становятся именами его атрибутов. При желании к вновь созданному классу можно было бы получить доступ, указав пространство имен Struct:

Struct.new("Address", :street, :city, :state)

books = Struct::Address.new("411 Elm St", "Dallas", "TX")

Создав структурный шаблон, вы вызываете его метод new для создания новых экземпляров данной конкретной структуры. Необязательно присваивать значения всем атрибутам в конструкторе. Опущенные атрибуты получат значение nil. После того как структура создана, к ее атрибутам можно обращаться с помощью обычного синтаксиса или указывая их имена в скобках в качестве индекса, как будто структура - это объект класса Hash. Более подробную информацию о классе Struct можно найти в любом справочном руководстве (например, на сайте ruby.doc.org).

Кстати, не рекомендуем создавать структуру с именем Tms, так как уже есть предопределенный класс Struct::Tms.

11.1.15. Замораживание объектов

Иногда необходимо воспрепятствовать изменению объекта. Это позволяет сделать метод freeze (определенный в классе Object). По существу, он превращает объект в константу.

Попытка модифицировать замороженный объект приводит к исключению TypeError. В листинге 11.8 приведено два примера.

Листинг 11.8. Замораживание объекта

str = "Это тест. "

str.freeze

begin

 str << " He волнуйтесь." # Попытка модифицировать.

rescue => err

 puts "#{err.class} #{err}"

end

arr = [1, 2, 3]

arr.freeze

begin

 arr << 4 # Попытка модифицировать.

rescue => err

 puts "#{err.class} #{err}"

end

# Выводится:

# TypeError: can't modify frozen string

# TypeError: can't modify frozen array

Однако имейте в виду, что метод freeze применяется к ссылке на объект, а не к переменной! Это означает, что любая операция, приводящая к созданию нового объекта, завершится успешно. Иногда это противоречит интуиции. В примере ниже мы ожидаем, что операция += не выполнится, но все работает нормально. Дело в том, что присваивание — не вызов метода. Эта операция воздействует на переменные, а не на объекты, поэтому новый объект создается беспрепятственно. Старый объект по-прежнему заморожен, но переменная ссылается уже не на него.

str = "counter-"

str.freeze

str += "intuitive" # "counter-intuitive"

arr = [8, 6, 7]

arr.freeze

arr += [5, 3, 0, 9] # [8, 6, 7, 5, 3, 0, 9]

Почему так происходит? Предложение a += x семантически эквивалентно a = a + x. При вычислении выражения a + x создается новый объект, который затем присваивается переменной a! Все составные операторы присваивания работают подобным образом, равно как и другие методы. Всегда задавайте себе вопрос: «Что я делаю — создаю новый объект или модифицирую существующий?» И тогда поведение freeze не станет для вас сюрпризом.

Существует метод frozen?, который сообщает, заморожен ли данный объект.

hash = { 1 => 1, 2 => 4, 3 => 9 }

hash.freeze

arr = hash.to_a

puts hash.frozen?  # true

puts arr.frozen?   # false

hash2 = hash

puts hash2.frozen? # true

Как видите (на примере hash2), замораживается именно объект, а не переменная.

11.2. Более сложные механизмы

Не все в модели ООП, реализованной в Ruby, одинаково очевидно. Что-то сложнее, что-то применяется реже. Линия раздела для каждого программиста проходит в разных местах. В этой части главы мы попытались собрать те средства, которые не так просты или не так часто встречаются в программах.

Иногда вы задаетесь вопросом, можно ли решить на Ruby ту или иную задачу. Краткий ответ таков: Ruby — богатый, динамический, объектно-ориентированный язык с широким набором разумно ортогональных средств; если нечто можно сделать на каком-то другом языке, то, скорее всего, можно и на Ruby.

Теоретически все полные по Тьюрингу языки более или менее одинаковы. Весь смысл проектирования языков в поиске осмысленной, удобной нотации. Читателю, сомневающемуся в важности нотации, стоит попробовать написать интерпретатор LISP на языке COBOL или выполнить деление чисел, записанных римскими цифрами.

Конечно, мы не хотим сказать, что любая задача на Ruby решается элегантно или естественно. Попытайся мы высказать такое утверждение, кто-нибудь очень быстро докажет, что мы не правы.

В этом разделе мы поговорим также о реализации на Ruby различных стилей программирования, например функционального и аспектно-ориентированного. Мы не претендуем на роль экспертов в этих областях, просто приводим мнение других. Относитесь к этому с долей скепсиса.

11.2.1. Отправка объекту явного сообщения

В статическом языке вы считаете очевидным, что имя вызываемой функции «зашито» в программу, это часть исходного текста. Динамический язык обладает в данном отношении большей гибкостью.

При любом вызове метода вы посылаете объекту сообщение. Обычно эти сообщения так же жестко «зашиты» в код, как и в статическом языке, но это необязательно. Можно написать программу, которая во время выполнения решает, какой метод вызывать. Метод send позволяет использовать Symbol для представления имени метода.

Пусть, например, имеется массив объектов, который нужно отсортировать, причем в качестве ключей сортировки хотелось бы использовать разные поля. Не проблема - можно просто написать специализированные блоки для сортировки. Но хотелось бы найти более элегантное решение, позволяющее обойтись одной процедурой, способной выполнить сортировку по любому указанному ключу. В листинге 11.9 такое решение приведено.

1 ... 81 82 83 84 85 86 87 88 89 ... 156
Перейти на страницу:
На этой странице вы можете бесплатно скачать Программирование на языке Ruby - Хэл Фултон торрент бесплатно.
Комментарии