使用来自另一个类的方法包装对象

时间:2011-11-04 21:46:05

标签: ruby-on-rails ruby methods

假设我有一个名为Article的模型:

class Article < ActiveRecord::Base
end

然后我有一个用于向文章对象(装饰器)添加行为的类:

class ArticleDecorator

  def format_title
  end

end

如果我想扩展一个文章对象的行为,我可以使ArticleDecorator成为一个模块,然后调用article.extend(ArticleDecorator),但我更喜欢这样的东西:

article = ArticleDecorator.decorate(Article.top_articles.first) # for single object

articles = ArticleDecorator.decorate(Article.all) # for collection of objects

我将如何实现这种装饰方法?

4 个答案:

答案 0 :(得分:5)

您对decorate方法的确切要求是什么?它应该只是为传递的对象添加一些新方法,还是应该使用相应的格式方法自动包装这些对象的方法?为什么你希望ArticleDecorator成为一个类而不仅仅是一个模块?

<强>更新

似乎来自 nathanvda 的解决方案就是您所需要的,但我建议使用更清洁的版本:

module ArticleDecorator

  def format_title
    "#{title} [decorated]"  
  end

  def self.decorate(object_or_objects_to_decorate)
    object_or_objects_to_decorate.tap do |objects|
      Array(objects).each { |obj| obj.extend ArticleDecorator }
    end
  end

end

它做同样的事情,但是:

  1. 避免检查依赖Kernel#Array方法的参数类型。
  2. 直接调用Object#extend(这是一种公共方法,因此无需通过send调用它。)
  3. Object#extend仅包含实例方法,因此我们可以将它们放在ArticleDecorator中,而不用其他模块包装它们。

答案 1 :(得分:1)

我可以提出一个不使用Module mixins的解决方案,从而为您提供更大的灵活性。例如,使用更像传统GoF装饰器的解决方案,您可以打开文章(如果应用了一次,则无法删除mixin)甚至允许您将包装的文章换成另一个文章。运行时。

这是我的代码:

class ArticleDecorator < BasicObject
  def self.[](instance_or_array)
    if instance_or_array.respond_to?(:to_a)
      instance_or_array.map {|instance| new(instance) }
    else
      new(instance_or_array)
    end
  end

  attr_accessor :wrapped_article

  def initialize(wrapped_article)
    @wrapped_article = wrapped_article
  end

  def format_title
    @wrapped_article.title.upcase
  end

  protected

  def method_missing(method, *arguments)
    @wrapped_article.method(method).call(*arguments)
  end
end

您现在可以通过调用

来扩展单个文章
extended_article = ArticleDecorator[article]

或通过调用

的多篇文章
articles = [article_a, article_b]
extended_articles = ArticleDecorator[articles]

您可以致电

重新获得原始文章
extended_article.wrapped_article

或者您可以像这样交换包装的文章

extended_article = ArticleDecorator[article_a]

extended_article.format_title
# => "FIRST"

extended_article.wrapped_article = article_b

extended_article.format_title
# => "SECOND"

因为ArticleDecorator扩展了BasicObject类,它几乎没有已定义的方法,所以即使#class和#object_id之类的东西对于包装的项也保持不变:

article.object_id
# => 123

extended_article = ArticleDecorator[article]

extended_article.object_id
# => 123

请注意,BasicObject仅存在于Ruby 1.9及更高版本中。

答案 2 :(得分:0)

你将扩展文章类实例,调用alias_method,并将其指向你想要的任何方法(虽然它听起来像一个模块,而不是一个类,至少现在是这样)。新版本获取返回值并像平常一样处理它。

在你的情况下,听起来你想要将“格式_。*”等内容与各自的属性获取者进行匹配。

哪部分让你绊倒?

答案 3 :(得分:0)

module ArticleDecorator
  def format_title
    "Title: #{title}"
  end
end

article = Article.top_articles.first.extend(ArticleDecorator) # for single object

应该可以正常工作。

articles = Article.all.extend(ArticleDecorator)

根据ActiveRecord对扩展一组对象的支持,也可以工作。

您也可以考虑使用ActiveSupport::Concern