Иногда возникает необходимость, запроксировать (обернуть) методы класса в свой код.
Например:
Итак допустим у нас есть некий класс, который тянет информацию о хабрчеловеке:
Использовать его мы будем так:
Самое простое решение, передавать новый экземпляр класса, нашему прокси классу:
Что бы это сделать, нам потребуется метод method_missing:
Теперь наш предыдущий код, можно использовать так:
Если сильно захотеть, то можно класс Proxy использовать как базовый при наследовании, для этого нам потребуется метод method_added(тут придется погуглить):
Что бы гарантировать, что методы вашего класса попадут в нашу обертку метода method_missing мы используем метод private, что сделать все публичные методы, приватными.
Так же вам придется, самим позаботится о правильной инициализации базового класса, изменив проксируемый класс так:
В действительности, это все черная магия, которая не будет 100% работать во всех случаях жизни.
Итак, как это работает?
method_missing — этот метод срабатывает в том случае, когда вы запрашиваете не существующий или же недоступный метод класса, он принимает три параметра это:
имя метода
аргументы
блок
Далее вы можете использовать метод send, который позволит вам вызвать любой метод любого класса, например:
method_added — этот метод срабатывает в тот момент времени, когда в класс добавляется новый метод, начиная именно с того места где вы его описали, т.е. если после method_added будут еще какие-то ваши методы, то он их поймает тоже.
Итак, все вместе, method_missing, дает нам возможность поймать не существующие или недоступные методы класса, что бы сделать реально существующие методы класса который наследует эту черную магию из базового класса, мы используем method_added, который регистрирует добавление новых методов и скрывает их от внешнего доступа через метод private, таким образом гарантирует попадание их в method_missing.
В котором, вы можете оборачивать их в любую дополнительную логику, не трогая класс, над которым вы работаете, не меняя код программы в которой вызывается запроксированный класс.
Например:
- запустить их в тредах
- навесить фильтры до и после
- померить производительность
Итак допустим у нас есть некий класс, который тянет информацию о хабрчеловеке:
Copy Source | Copy HTML
- require 'rubygems'
- require 'open-uri'
- require 'iconv'
- require 'nokogiri'
-
- $KCODE = 'u'
-
- class Habr
- attr_accessor :user, :uid
- def initialize(user)
- @user = user
- @uid = get_uid
- end
- def get(url)
- Nokogiri::HTML(open(url))
- end
- def get_uid
- html = get("http://#{@user}.habrahabr.ru")
- html.css("div.karma-holder")[0].get_attribute('id')
- end
- def get_rating
- result = []
- html = get("http://#{@user}.habrahabr.ru")
- html.css("div[@id='#{@uid}']").each{|el|
- result << el.css('span.mark')[0].content.gsub!(',','.').to_f
- result << el.css('span.number')[0].content.gsub!(',','.').to_f
- result << el.css('dd.total')[0].content.to_i
- }
- result
- end
- def get_posts
- result = []
- html = get("http://#{@user}.habrahabr.ru/blog/")
- html.css("div[@id='main-content'] a.topic").each{|el|
- result << { :title => el.content, :url => el.attribute('href') }
- }
- result
- end
- end
Использовать его мы будем так:
Copy Source | Copy HTML
- info = Habr.new('tenkoff')
- p info.get_rating
- p info.get_posts
Самое простое решение, передавать новый экземпляр класса, нашему прокси классу:
Copy Source | Copy HTML
- proxy = Proxy.new( ProxedClass.new )
Что бы это сделать, нам потребуется метод method_missing:
Copy Source | Copy HTML
- class Proxy
- def initialize(obj)
- @class = obj || self
- end
- def method_missing(name, *args, &block)
- p "before ##{name}"
- result = @class.send name, *args, &block
- p "after ##{name}"
- result
- end
- end
Теперь наш предыдущий код, можно использовать так:
Copy Source | Copy HTML
- info = Proxy.new(Habr.new('tenkoff'))
- p info.get_rating
- p info.get_posts
Если сильно захотеть, то можно класс Proxy использовать как базовый при наследовании, для этого нам потребуется метод method_added(тут придется погуглить):
Copy Source | Copy HTML
- class Proxy
- def initialize(obj = '')
- @class = obj || self
- end
- def method_missing(name, *args, &block)
- p "before ##{name}"
- result = @class.send name, *args, &block
- p "after ##{name}"
- result
- end
- def self.method_added(method)
- if self.public_method_defined? method
- private method
- end
- end
- end
Что бы гарантировать, что методы вашего класса попадут в нашу обертку метода method_missing мы используем метод private, что сделать все публичные методы, приватными.
Так же вам придется, самим позаботится о правильной инициализации базового класса, изменив проксируемый класс так:
Copy Source | Copy HTML
- def initialize(user)
- @user = user
- @uid = get_uid
- super(self)
- end
В действительности, это все черная магия, которая не будет 100% работать во всех случаях жизни.
Итак, как это работает?
method_missing — этот метод срабатывает в том случае, когда вы запрашиваете не существующий или же недоступный метод класса, он принимает три параметра это:
имя метода
аргументы
блок
Далее вы можете использовать метод send, который позволит вам вызвать любой метод любого класса, например:
Copy Source | Copy HTML
- irb(main):001:0> Kernel.send :print, 'hello world'
- hello world=> nil
-
method_added — этот метод срабатывает в тот момент времени, когда в класс добавляется новый метод, начиная именно с того места где вы его описали, т.е. если после method_added будут еще какие-то ваши методы, то он их поймает тоже.
Итак, все вместе, method_missing, дает нам возможность поймать не существующие или недоступные методы класса, что бы сделать реально существующие методы класса который наследует эту черную магию из базового класса, мы используем method_added, который регистрирует добавление новых методов и скрывает их от внешнего доступа через метод private, таким образом гарантирует попадание их в method_missing.
В котором, вы можете оборачивать их в любую дополнительную логику, не трогая класс, над которым вы работаете, не меняя код программы в которой вызывается запроксированный класс.