Pull to refresh

Ruby — проксирование методов класса

Reading time 4 min
Views 3.7K
Иногда возникает необходимость, запроксировать (обернуть) методы класса в свой код.

Например:
  • запустить их в тредах
  • навесить фильтры до и после
  • померить производительность


Итак допустим у нас есть некий класс, который тянет информацию о хабрчеловеке:
Copy Source | Copy HTML
  1. require 'rubygems'
  2. require 'open-uri'
  3. require 'iconv'
  4. require 'nokogiri'
  5.  
  6. $KCODE = 'u'
  7.  
  8. class Habr
  9.   attr_accessor :user, :uid
  10.     def initialize(user)
  11.       @user = user
  12.       @uid = get_uid
  13.     end
  14.     def get(url)
  15.       Nokogiri::HTML(open(url))
  16.     end
  17.     def get_uid
  18.       html = get("http://#{@user}.habrahabr.ru")
  19.       html.css("div.karma-holder")[0].get_attribute('id')
  20.     end
  21.     def get_rating
  22.       result = []
  23.       html = get("http://#{@user}.habrahabr.ru")
  24.       html.css("div[@id='#{@uid}']").each{|el|
  25.           result << el.css('span.mark')[0].content.gsub!(',','.').to_f
  26.           result << el.css('span.number')[0].content.gsub!(',','.').to_f
  27.           result << el.css('dd.total')[0].content.to_i
  28.       }
  29.       result
  30.     end
  31.     def get_posts
  32.     result = []
  33.     html = get("http://#{@user}.habrahabr.ru/blog/")
  34.     html.css("div[@id='main-content'] a.topic").each{|el|
  35.       result << { :title => el.content, :url => el.attribute('href') }
  36.     }
  37.     result
  38.     end
  39. end

Использовать его мы будем так:
Copy Source | Copy HTML
  1. info = Habr.new('tenkoff')
  2. p info.get_rating
  3. p info.get_posts

Самое простое решение, передавать новый экземпляр класса, нашему прокси классу:
Copy Source | Copy HTML
  1. proxy = Proxy.new( ProxedClass.new )

Что бы это сделать, нам потребуется метод method_missing:
Copy Source | Copy HTML
  1. class Proxy
  2.   def initialize(obj)
  3.       @class = obj || self
  4.     end
  5.   def method_missing(name, *args, &block)
  6.     p "before ##{name}"
  7.     result = @class.send name, *args, &block
  8.     p "after ##{name}"
  9.     result
  10.   end
  11. end

Теперь наш предыдущий код, можно использовать так:
Copy Source | Copy HTML
  1. info = Proxy.new(Habr.new('tenkoff'))
  2. p info.get_rating
  3. p info.get_posts

Если сильно захотеть, то можно класс Proxy использовать как базовый при наследовании, для этого нам потребуется метод method_added(тут придется погуглить):
Copy Source | Copy HTML
  1. class Proxy
  2.   def initialize(obj = '')
  3.       @class = obj || self
  4.     end
  5.   def method_missing(name, *args, &block)
  6.     p "before ##{name}"
  7.     result = @class.send name, *args, &block
  8.     p "after ##{name}"
  9.     result
  10.   end
  11.   def self.method_added(method)
  12.     if self.public_method_defined? method
  13.       private method
  14.     end
  15.   end
  16. end

Что бы гарантировать, что методы вашего класса попадут в нашу обертку метода method_missing мы используем метод private, что сделать все публичные методы, приватными.

Так же вам придется, самим позаботится о правильной инициализации базового класса, изменив проксируемый класс так:
Copy Source | Copy HTML
  1. def initialize(user)
  2.   @user = user
  3.   @uid = get_uid
  4.   super(self)
  5. end


В действительности, это все черная магия, которая не будет 100% работать во всех случаях жизни.

Итак, как это работает?
method_missing — этот метод срабатывает в том случае, когда вы запрашиваете не существующий или же недоступный метод класса, он принимает три параметра это:
имя метода
аргументы
блок

Далее вы можете использовать метод send, который позволит вам вызвать любой метод любого класса, например:
Copy Source | Copy HTML
  1. irb(main):001:0> Kernel.send :print, 'hello world'
  2. hello world=> nil
  3.  


method_added — этот метод срабатывает в тот момент времени, когда в класс добавляется новый метод, начиная именно с того места где вы его описали, т.е. если после method_added будут еще какие-то ваши методы, то он их поймает тоже.

Итак, все вместе, method_missing, дает нам возможность поймать не существующие или недоступные методы класса, что бы сделать реально существующие методы класса который наследует эту черную магию из базового класса, мы используем method_added, который регистрирует добавление новых методов и скрывает их от внешнего доступа через метод private, таким образом гарантирует попадание их в method_missing.

В котором, вы можете оборачивать их в любую дополнительную логику, не трогая класс, над которым вы работаете, не меняя код программы в которой вызывается запроксированный класс.
Tags:
Hubs:
+11
Comments 26
Comments Comments 26

Articles