用于可扩展处理程序/插件体系结构的Ruby结构

时间:2011-08-05 10:27:49

标签: ruby class design-patterns plugins module

我写的东西有点像Facebook的共享链接预览。

我想通过为每个我想编写自定义解析器的新网站添加一个新文件,使其可以轻松扩展到新网站。我已经找到了设计模式的基本概念,但没有足够的模块经验来确定细节。我确信在其他项目中有很多类似的例子。

结果应该是这样的:

> require 'link'
=> true
> Link.new('http://youtube.com/foo').preview
=> {:title => 'Xxx', :description => 'Yyy', :embed => '<zzz/>' }
> Link.new('http://stackoverflow.com/bar').preview
=> {:title => 'Xyz', :description => 'Zyx' }

代码就是这样:

#parsers/youtube.rb
module YoutubeParser
  url_match /(youtube\.com)|(youtu.be)\//
  def preview
    get_stuff_using youtube_api
  end
end

#parsers/stackoverflow.rb
module SOFParser
  url_match /stachoverflow.com\//
  def preview
    get_stuff
  end
end

#link.rb
class Link
   def initialize(url)
     extend self with the module that has matching regexp
   end
end

3 个答案:

答案 0 :(得分:3)

# url_processor.rb
class UrlProcessor
  # registers url handler for given pattern
  def self.register_url pattern, &block
    @patterns ||= {}
    @patterns[pattern] = block
  end

  def self.process_url url
    _, handler = @patterns.find{|p, _| url =~ p}
    if handler
      handler.call(url)
    else
      {}
    end
  end
end

# plugins/so_plugin.rb
class SOPlugin
  UrlProcessor.register_url /stackoverflow\.com/ do |url|
    {:title => 'foo', :description => 'bar'}
  end
end

# plugins/youtube_plugin.rb
class YoutubePlugin
  UrlProcessor.register_url /youtube\.com/ do |url|
    {:title => 'baz', :description => 'boo'}
  end
end

p UrlProcessor.process_url 'http://www.stackoverflow.com/1234'
#=>{:title=>"foo", :description=>"bar"}
p UrlProcessor.process_url 'http://www.youtube.com/1234'
#=>{:title=>"baz", :description=>"boo"}
p UrlProcessor.process_url 'http://www.foobar.com/1234'
#=>{}

你只需要从插件目录中require每个.rb。

答案 1 :(得分:0)

如果您愿意采用这种方法,您应该扫描字段中的mathing字符串,然后include扫描正确字符串。

在同样的情况下,我试图采用不同的方法。我正在使用新方法扩展模块,@ @注册它们,这样我就不会注册两个同名的方法。到目前为止它运作良好,虽然我开始的项目远远没有离开特定网站的一个混乱的特定领域。

这是主文件。

module Onigiri
  extend self
  @@registry ||= {}

  class OnigiriHandlerTaken < StandardError
    def description
      "There was an attempt to override registered handler. This usually indicates a bug in Onigiri."
    end
  end

  def clean(data, *params)
    dupe = Onigiri::Document.parse data
    params.flatten.each do |method|
      dupe = dupe.send(method) if @@registry[method]
    end
    dupe.to_html
  end

  class Document < Nokogiri::HTML::DocumentFragment
  end

  private

  def register_handler(name)
    unless @@registry[name]
      @@registry[name] = true
    else
      raise OnigiriHandlerTaken
    end
  end

end

这是扩展文件。

# encoding: utf-8
module Onigiri
  register_handler :fix_backslash
  class Document
    def fix_backslash
      dupe = dup
      attrset = ['src', 'longdesc', 'href', 'action']
      dupe.css("[#{attrset.join('], [')}]").each do |target|
        attrset.each do |attr|
          target[attr] = target[attr].gsub("\\", "/") if target[attr]
        end
      end
      dupe
    end
  end
end

我看到的另一种方法是使用一组不同的(但行为无法区分的)类和一个简单的决策机制来调用正确的类。包含类名和相应的url_matcher的简单哈希可能就足够了。

希望这有帮助。

答案 2 :(得分:0)

我想我已经钉了它。

irb(main):001:0> require './url_handler'
=> true
irb(main):002:0> UrlHandler.new('www.youtube.com').process
=> {:description=>"Nyan nyan!", :title=>"Youtube"}
irb(main):003:0> UrlHandler.new('www.facebook.com').process
=> {:description=>"Hello!", :title=>"Facebook"}
irb(main):004:0> UrlHandler.new('www.stackoverflow.com').process
=> {:description=>"Title fetcher", :title=>"Generic"}

url_handler.rb:

class UrlHandler
   attr_accessor :url
   def initialize(url)
     @url=url
     if plugin=Module.url_pattern.find{|re, plugin| @url.match(re)}
       extend plugin.last
     else
       extend HandlerPlugin::Generic
     end
   end
end

class Module
   def url_pattern(pattern=nil)
     @@patterns ||= {}
     @@patterns[pattern] ||= self unless pattern.nil?
     return @@patterns
   end
end

module HandlerPlugin
  module Generic
    def process
      {:title => 'Generic', :description => 'Title fetcher'}
    end
  end
end

Dir[File.join(File.dirname(__FILE__), 'plugins', '*.rb')].each {|file| require file }

plugins / youtube.rb(facebook.rb非常相似)

module HandlerPlugin::Youtube
  include HandlerPlugin
  url_pattern /youtube/
  def process
    {:title => 'Youtube', :description => 'Nyan nyan!'}
  end
end

像这样污染模块可能不太好,但到目前为止,这是我能想到的最好的解决方案。