假设我有一个名为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
我将如何实现这种装饰方法?
答案 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
它做同样的事情,但是:
Kernel#Array
方法的参数类型。Object#extend
(这是一种公共方法,因此无需通过send
调用它。)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。