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

11.2.2. Специализация отдельного объекта

11.2.2. Специализация отдельного объекта

Я солипсист и, признаться, удивлен, что большинство из нас таковыми не являются.

Из письма, полученного Бертраном Расселом

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

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

Слово «синглет» может стать источником путаницы, потому что оно употребляется и в другом смысле - как название хорошо известного паттерна проектирования, описывающего класс, для которого может существовать лишь один объект. Если вас интересует такое использование, обратитесь к библиотеке singleton.rb.

В следующем примере мы видим два объекта, оба строки. Для второго мы добавляем метод upcase, который переопределяет существующий метод с таким же именем.

а = "hello"
b = "goodbye"
def b.upcase # Создать синглетный метод.
 gsub(/(.)(.)/) { $1.upcase + $2 }
end
puts a.upcase # HELLO
puts b.upcase # GoOdBye

Добавление синглетного метода к объекту порождает синглетный класс для данного объекта, если он еще не был создан ранее. Родителем синглетного класса является исходный класс объекта. (Можно считать, что это анонимный подкласс исходного класса.) Если вы хотите добавить к объекту несколько методов, то можете создать синглетный класс явно:

b = "goodbye"
class << b
 def upcase # Создать синглетный метод.
  gsub(/(.){.)/) { $1.upcase + $2 }
 end
 def upcase!
  gsub!(/(.)(.)/) { $1.upcase + $2 }
 end
end
puts b.upcase # GoOdBye
puts b        # goodbye
b.upcase!
puts b        # GoOdBye

Отметим попутно, что у более «примитивных» объектов (например, Fixnum) не может быть добавленных синглетных методов. Связано это с тем, что такие объекты хранятся как непосредственные значения, а не как ссылки. Впрочем, реализация подобной функциональности планируется в будущих версиях Ruby (хотя непосредственные значения сохранятся).

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

class SomeClass
 # Stuff...
 class << self
  # Какой-то код
 end
 # ...продолжение.
end

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

class TheClass
 class << self
  def hello
   puts "hi"
  end
 end
end
# вызвать метод класса
TheClass.hello # hi

Еще одно распространенное применение такой техники — определение на уровне класса вспомогательных функций, к которым можно обращаться из других мест внутри определения класса. Например, мы хотим определить несколько функций доступа, которые преобразуют результат своей работы в строку. Можно, конечно, написать отдельно код каждой такой функции. Но есть и более элегантное решение — определить функцию уровня класса accessor_string, которая сгенерирует необходимые нам функции (как показано в листинге 11.10).

Листинг 11.10. Метод уровня класса accessor_string

сlass MyClass
 class << self
  def accessor_string(*names)
   names.each do |name|
    class_eval <<-EOF
    def #{name}
     @#{name}.to_s
    end
    EOF
   end
  end
 end
 def initialize
  @a = [1,2,3]
  @b = Time.now
 end
 accessor_string :a, :b
end
о = MyClass.new
puts o.a # 123
puts o.b # Mon Apr 30 23:12:15 CDT 2001

Вы наверняка сможете придумать и другие, более изобретательные применения. Метод extend подмешивает к объекту модуль. Методы экземпляра, определенные в модуле, становятся методами экземпляра объекта. Взгляните на листинг 11.11.

Листинг 11.11. Использование метода extend

module Quantifier
 def any?
  self.each { |x| return true if yield x }
  false
 end
 def all?
  self.each { |x| return false if not yield x }
  true
 end
end
list = [1, 2, 3, 4, 5]
list.extend(Quantifier)
flag1 = list.any? {|x| x > 5 }      # false
flag2 = list.any? {|x| x >= 5 }     # true
flag3 = list.all? {|x| x <= 10 }    # true
flag4 = list.all? {|x| x % 2 == 0 } # false

В этом примере к массиву list подмешаны методы any? и all?.

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


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