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

20.2. Пример: эмуляция биржевой ленты

20.2. Пример: эмуляция биржевой ленты

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

Но мы добавили одну тонкость. Не желая следить за малейшими колебаниями цен, мы реализовали модуль Observer, который позволяет подписаться на информационный канал. Клиент следит за поступающими сведениями и предупреждает нас, когда изменение цены превысит заданный порог.

Сначала рассмотрим модуль DrbObservable. Это прямолинейная реализация паттерна Observer (Наблюдатель), описанного в замечательной книге Э. Гаммы, Р. Хелма, Р. Джонсона и Дж. Влиссидеса «Паттерны проектирования» (см. сноску в разделе 12.3.1). Еще этот паттерн называют «Издатель-Подписчик».

В листинге 20.1 наблюдатель определен как объект, отвечающий на вызов метода update. Сервер добавляет наблюдателей по их просьбе и посылает им уведомления, обращаясь к методу notify_observers.

Листинг 20.1. Модуль DrbObservable

module DRbObservable
 def add_observer(observer)
  @observer_peers ||= []
  unless observer.respond_to? :update
   raise NameError, "наблюдатель должен отвечать на вызов 'update'"
  end
  @observer_peers.push observer
 end
 def delete_observer(observer)
  @observer_peers.delete observer if defined? @observer_peers
 end
 def notify_observers(*arg)
  return unless defined? @observer_peers
  for i in @observer_peers.dup
   begin
    i.update(*arg)
   rescue
    delete_observer(i)
   end
  end
 end
end

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

Листинг 20.2. Канал биржевых котировок (drb-сервер)

require "drb"
require "drb_pbserver"
# Генерировать случайные котировки.
class MockPrice
 MIN = 75
 RANGE = 50
 def initialize(symbol)
  @price = RANGE / 2
 end
 def price
  @price += (rand() - 0.5)*RANGE
  if @price < 0
   @price = -@price
  elsif @price >= RANGE
   @price = 2*RANGE - @price
  end
  MIN + @price
 end
end
class Ticker # Периодически получать котировку акций.
 include DRbObservable
 def initialize(price_feed)
  @feed = price_feed
  Thread.new { run }
 end
 def run
  lastPrice = nil
  loop do
   price = @feed.price
   print "Текущая котировка: #{price}n"
   if price != lastPrice
    lastPrice = price
    notify_observers(Time.now, price)
   end
   sleep 1
  end
 end
end
ticker = Ticker.new(MockPrice.new("MSFT"))
DRb.start_service('druby://localhost:9001', ticker)
puts 'Нажмите [return] для завершения.'
gets

На платформе Windows примененный способ завершения программы вызывает сложности. Функция gets в этом случае может блокировать главный поток. Если вы это видите, попробуйте вместо обращения к gets поставить DRb.thread.join (а завершайте программу нажатием Ctrl+C).

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

Листинг 20.3. Наблюдатель биржевых котировок (drb-клиент)

require "drb"
class Warner
 include DRbUndumped
 def initialize(ticker, limit)
  @limit = limit
  ticker.add_observer(self) # Любой объект Warner
                            # является наблюдателем.
 end
end
class WarnLow < Warner
 def update(time, price)    # Обратный вызов наблюдателя.
  if price < @limit
   print "--- #{time.to_s}: Цена ниже #@limit: #{price}n"
  end
 end
end
class WarnHigh < Warner
 def update(time, price)    # Обратный вызов наблюдателя.
  if price > @limit
   print "+++ #{time.to_s}: Цена выше #@limit: #{price}n"
  end
 end
end
DRb.start_service
ticker = DRbObject.new(nil, "druby://localhost:9001")
WarnLow.new(ticker, 90)
WarnHigh.new(ticker, 110)
puts 'Нажмите [return] для завершения.'
gets

Модуль DRbUndumped (см. листинге 20.3) следует включать в любой объект, который не нужно подвергать маршалингу. Самого присутствия этого модуля в числе предков объекта достаточно, чтобы drb не пытался применять к нему маршалинг. Вот исходный текст этого модуля целиком:

module DrbUndumped
def _dump(dummy)
raise TypeError, "can't dump"
end
end

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

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


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