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

时间:2022-09-25 10:11:05

Let's say I have a model called Article:

假设我有一个叫做Article的模型

class Article < ActiveRecord::Base
end

And then I have a class that is intended to add behavior to an article object (a decorator):

然后我有一个类,它打算将行为添加到一个article对象(一个decorator):

class ArticleDecorator

  def format_title
  end

end

If I wanted to extend behavior of an article object, I could make ArticleDecorator a module and then call article.extend(ArticleDecorator), but I'd prefer something like this:

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

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

or

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

How would I go about implementing this decorate method?

我该如何实施这个装修方法呢?

4 个解决方案

#1


5  

What exactly do you want from decorate method? Should it simply add some new methods to passed objects or it should automatically wrap methods of these objects with corresponding format methods? And why do you want ArticleDecorator to be a class and not just a module?

你想要什么样的装修方法?它应该简单地向传递的对象添加一些新方法,还是应该用相应的格式方法自动包装这些对象的方法?为什么您希望ArticleDecorator是一个类而不仅仅是一个模块呢?

Updated:

更新:

Seems like solution from nathanvda is what you need, but I'd suggest a bit cleaner version:

看来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

It does the same thing, but:

它做同样的事情,但是:

  1. Avoids checking type of the arguments relying on Kernel#Array method.
  2. 避免使用内核#数组方法检查参数的类型。
  3. Calls Object#extend directly (it's a public method so there's no need in invoking it through send).
  4. 直接调用对象#扩展(它是一个公共方法,所以不需要通过send调用它)。
  5. Object#extend includes only instance methods so we can put them right in ArticleDecorator without wrapping them with another module.
  6. 对象#extend只包含实例方法,因此我们可以将它们放在ArticleDecorator中,而无需使用另一个模块包装它们。

#2


1  

May I propose a solution which is not using Module mixins and thereby granting you more flexibility. For example, using a solution a bit more like the traditional GoF decorator, you can unwrap your Article (you can't remove a mixin if it is applied once) and it even allows you to exchange the wrapped Article for another one in runtime.

我可以提出一个不使用模块混合的解决方案,从而给您更多的灵活性吗?例如,使用类似于传统GoF decorator的解决方案,您可以展开您的文章(如果只应用一次,就不能删除一个mixin),甚至还可以在运行时将包装好的文章与另一篇文章交换。

Here is my code:

这是我的代码:

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

You can now extend a single Article by calling

现在可以通过调用来扩展一篇文章。

extended_article = ArticleDecorator[article]

or multiple articles by calling

或者通过调用多个文章

articles = [article_a, article_b]
extended_articles = ArticleDecorator[articles]

You can regain the original Article by calling

您可以通过调用重新获得原始文章

extended_article.wrapped_article

Or you can exchange the wrapped Article inside like this

或者你也可以像这样交换包装好的物品

extended_article = ArticleDecorator[article_a]

extended_article.format_title
# => "FIRST"

extended_article.wrapped_article = article_b

extended_article.format_title
# => "SECOND"

Because the ArticleDecorator extends the BasicObject class, which has almost no methods already defined, even things like #class and #object_id stay the same for the wrapped item:

因为ArticleDecorator扩展了BasicObject类,它几乎没有定义任何方法,甚至像#class和#object_id这样的东西对于包装的项目也保持不变:

article.object_id
# => 123

extended_article = ArticleDecorator[article]

extended_article.object_id
# => 123

Notice though that BasicObject exists only in Ruby 1.9 and above.

请注意,尽管BasicObject仅存在于Ruby 1.9及以上版本中。

#3


0  

You'd extend the article class instance, call alias_method, and point it at whatever method you want (although it sounds like a module, not a class, at least right now). The new version gets the return value and processes it like normal.

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

In your case, sounds like you want to match up things like "format_.*" to their respective property getters.

在您的例子中,听起来您想要匹配“format_”之类的东西。*“给他们各自的财产持有人。

Which part is tripping you up?

是哪一部分把你绊倒了?

#4


0  

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

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

Should work fine.

可正常工作。

articles = Article.all.extend(ArticleDecorator)

May also work depending on ActiveRecord support for extending a set of objects.

也可以使用ActiveRecord支持扩展一组对象。

You may also consider using ActiveSupport::Concern.

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

#1


5  

What exactly do you want from decorate method? Should it simply add some new methods to passed objects or it should automatically wrap methods of these objects with corresponding format methods? And why do you want ArticleDecorator to be a class and not just a module?

你想要什么样的装修方法?它应该简单地向传递的对象添加一些新方法,还是应该用相应的格式方法自动包装这些对象的方法?为什么您希望ArticleDecorator是一个类而不仅仅是一个模块呢?

Updated:

更新:

Seems like solution from nathanvda is what you need, but I'd suggest a bit cleaner version:

看来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

It does the same thing, but:

它做同样的事情,但是:

  1. Avoids checking type of the arguments relying on Kernel#Array method.
  2. 避免使用内核#数组方法检查参数的类型。
  3. Calls Object#extend directly (it's a public method so there's no need in invoking it through send).
  4. 直接调用对象#扩展(它是一个公共方法,所以不需要通过send调用它)。
  5. Object#extend includes only instance methods so we can put them right in ArticleDecorator without wrapping them with another module.
  6. 对象#extend只包含实例方法,因此我们可以将它们放在ArticleDecorator中,而无需使用另一个模块包装它们。

#2


1  

May I propose a solution which is not using Module mixins and thereby granting you more flexibility. For example, using a solution a bit more like the traditional GoF decorator, you can unwrap your Article (you can't remove a mixin if it is applied once) and it even allows you to exchange the wrapped Article for another one in runtime.

我可以提出一个不使用模块混合的解决方案,从而给您更多的灵活性吗?例如,使用类似于传统GoF decorator的解决方案,您可以展开您的文章(如果只应用一次,就不能删除一个mixin),甚至还可以在运行时将包装好的文章与另一篇文章交换。

Here is my code:

这是我的代码:

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

You can now extend a single Article by calling

现在可以通过调用来扩展一篇文章。

extended_article = ArticleDecorator[article]

or multiple articles by calling

或者通过调用多个文章

articles = [article_a, article_b]
extended_articles = ArticleDecorator[articles]

You can regain the original Article by calling

您可以通过调用重新获得原始文章

extended_article.wrapped_article

Or you can exchange the wrapped Article inside like this

或者你也可以像这样交换包装好的物品

extended_article = ArticleDecorator[article_a]

extended_article.format_title
# => "FIRST"

extended_article.wrapped_article = article_b

extended_article.format_title
# => "SECOND"

Because the ArticleDecorator extends the BasicObject class, which has almost no methods already defined, even things like #class and #object_id stay the same for the wrapped item:

因为ArticleDecorator扩展了BasicObject类,它几乎没有定义任何方法,甚至像#class和#object_id这样的东西对于包装的项目也保持不变:

article.object_id
# => 123

extended_article = ArticleDecorator[article]

extended_article.object_id
# => 123

Notice though that BasicObject exists only in Ruby 1.9 and above.

请注意,尽管BasicObject仅存在于Ruby 1.9及以上版本中。

#3


0  

You'd extend the article class instance, call alias_method, and point it at whatever method you want (although it sounds like a module, not a class, at least right now). The new version gets the return value and processes it like normal.

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

In your case, sounds like you want to match up things like "format_.*" to their respective property getters.

在您的例子中,听起来您想要匹配“format_”之类的东西。*“给他们各自的财产持有人。

Which part is tripping you up?

是哪一部分把你绊倒了?

#4


0  

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

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

Should work fine.

可正常工作。

articles = Article.all.extend(ArticleDecorator)

May also work depending on ActiveRecord support for extending a set of objects.

也可以使用ActiveRecord支持扩展一组对象。

You may also consider using ActiveSupport::Concern.

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