在Ruby中使用其他行为来装饰对象的界面?

时间:2014-10-28 16:21:23

标签: ruby

我有一个Ruby对象,它将事件分派给第三方服务:

class Dispatcher
  def track_event(e)
    ThirdPartyService.track e.id, e.name, my_api_key
  end
end

使用ThirdPartyService可能会引发错误(例如,如果没有可用的网络连接)。 Dispatcher的消费者通常适合决定如何处理这些消费者,而不是Dispatcher本身。

我们如何以对象出现以使用Dispatcher的方式来装饰Dispatcher的使用,但所有异常都会被捕获并记录下来?也就是说,我希望能够写下:

obj.track_event(...)

但要抓住例外情况。

3 个答案:

答案 0 :(得分:2)

Thoughtbot在different ways to implement decorators in Ruby上有一篇很棒的博文。这个("模块+扩展+超级装饰")特别简洁:

module ErrorLoggingMixin
  def track_event(event)
    super
  rescue ex
    Logger.warn(ex)
  end
end

obj = Dispatcher.new          # initialize a Dispatcher as usual
obj.extend(ErrorLoggingMixin) # extend it with the new behavior

obj.track_event(some_event)   # call its methods as usual

博客文章列出了这些优点和缺点:

  

此实施的好处是:

     
      
  • 它通过所有装饰者代理
  •   
  • 它具有所有原始界面,因为它是原始对象
  •   
     

此实现的缺点是:

     
      
  • 不能在同一个对象上多次使用相同的装饰器   *很难分辨哪个装饰者添加了功能
  •   

我建议您阅读帖子的其余部分,了解其他实施方案,并做出最适合您需求的选择。

答案 1 :(得分:1)

我最接近的想法是在一个方法中包含track_event的用法,该方法提供了一个捕获异常的块,如下所示:

module DispatcherHelper
  def self.dispatch(&block)
    dispatcher = Dispatcher.new
    begin
      yield dispatcher
    rescue NetworkError
      # ...
    rescue ThirdPartyError
      # ...
    end
  end
end

这样我们就可以了:

DispatcherHelper.dispatch { |d| d.track_event(...) }

我能看到的另一种选择是模仿track_event的签名,以便你得到:

module DispatcherHelper
  def self.track_event(e)
    begin
      Dispatcher.new.track_event(e)
    rescue NetworkError
      # ...
    rescue ThirdPartyError
      # ...
    end
  end
end

但我不太喜欢它,因为它将签名连接在一起。

答案 2 :(得分:0)

使用Bang方法

这样做的方法不止一种。实现目标的一种方法是进行小规模的重构,将异常提升方法重新定义为“谨慎”#34;方法,将其设为私有,并使用更安全的"公共接口中的无爆炸方法。例如:

class Dispatcher
  def track_event(e)
    result = track_event! e rescue nil
    result ? result : 'Exception handled here.'
  end

  private

  def track_event! e
    ThirdPartyService.track e.id, e.name, my_api_key
  end
end

使用重构代码会在ThirdPartyService引发异常时产生以下内容:

Dispatcher.new.track_event 1
#=> "Exception handled here."

当然还有其他方法可以解决这类问题。很大程度上取决于您的代码试图表达的内容。因此,您的里程可能会有所不同。