Ruby包装函数

时间:2013-07-03 23:14:31

标签: ruby metaprogramming

我有一个包装其他实例的类来提供额外的功能(演示者),并希望现在拥有提供相同功能的子类。所以它会是这样的:

class BasePresenter
  attr_accessor :base_object

  def initialize(base_object)
    self.base_object = base_object
  end
end

class WalrusPresenter < BasePresenter
end

我希望能够做到这一点:

BasePresenter(:bubbles)
  #=> <# BasePresenter @base_object=:bubbles >

WalrusPresenter(:frank)
  #=> <# WalrusPresenter @base_object=:frank >

更新

我认为功能差异超出了问题的范围,但它们似乎是一个棘手的问题所以我会添加它们。

我不是要委托.new

BasePresenter.new只接受一个参数并将其包装在演示者中。 BasePresenter()获取一个对象并且:

  • 如果已经是演示者,请将其退回
  • 如果它是一个对象数组,则映射它们并创建一个新的演示者
  • 如果是单个对象,请将其包装在演示者中并将其返回。

这更接近ActiveSupport的Array#wrap,但我认为括号语法相当于其功能的交流,所以我想尽可能使用它。继承部分正在绊倒我;将它定义为基类,这样它就可以用于所有孩子。

BasePresenter(:bubbles)
  #=> <# BasePresenter @base_object=:bubbles >

BasePresenter([:waldorf, :statler])
  #=> [ <# BasePresenter @base_object=:waldorf >, <# BasePresenter @base_object=:staler> ]

WalrusPresenter.new(:frank)
  #=> <# WalrusPresenter @base_object=:frank >
WalrusPresenter([:harold, :richard])
  #=> [ <# WalrusPresenter @base_object=:harold >, <# WalrusPresenter @base_object=:richard > ]

WalrusPresenter(WalrusPresenter(WalrusPresenter(:frank)))
  #=> <# WalrusPresenter @base_object=:frank >

3 个答案:

答案 0 :(得分:4)

我可能会忽略您的问题,但对我而言,您似乎忘记使用new创建类的实例:

BasePresenter.new(:bubbles)
# => #<BasePresenter:0x00000001ae33b8 @base_object=:bubbles> 
WalrusPresenter.new(:frank)
# => #<WalrusPresenter:0x00000001ae7878 @base_object=:frank> 

更新: Mischa已经以与我相同的方式回复了您的评论。 Kernel#Array尝试在其参数上调用to_ary,然后尝试在其上调用to_a,如果失败,则创建一个Array,其中参数作为唯一元素,如果失败的话。

目前尚不清楚你想要什么样的行为;看起来你只想用new来解决,当然你可以这样做,但这很愚蠢:

def BasePresenter(base)
  BasePresenter.new(base)
end

def WalrusPresenter(base)
  WalrusPresenter.new(base)
end

你可以做一些元编程,以避免在创建包装器时重复自己。但我没有看到你想要避免new的原因,并且没有充分的理由这样做,我不得不建议反对它。 Ruby使用new来实例化对象。

更新2:感谢您澄清所需内容。这是我想到的第一件事。你肯定可以清理一下,但这样的事情应该让你开始。 (wrap根本不需要BasePresenter。)无论如何,你走了:

class BasePresenter
  attr_accessor :base_object

  def initialize(base_object)
    self.base_object = base_object
  end

  def self.wrap klass
    Object.class_eval do
      define_method klass.to_s do |object|
        case object
        when BasePresenter
          object
        when Array
          object.map{|base| klass.new(base)}
        else
          klass.new(object)
        end
      end
    end
  end

  BasePresenter.wrap BasePresenter

  def self.inherited child_klass
    BasePresenter.wrap child_klass
  end
end

class WalrusPresenter < BasePresenter
end

这似乎可以做你想要的:

BasePresenter(:bubbles)
# => #<BasePresenter:0x00000001db05a0 @base_object=:bubbles> 

BasePresenter([:waldorf, :statler])
# => [#<BasePresenter:0x00000001da7c98 @base_object=:waldorf>,
      #<BasePresenter:0x00000001da7c70 @base_object=:statler>] 

WalrusPresenter.new(:frank)
# => #<WalrusPresenter:0x00000001da4070 @base_object=:frank> 

WalrusPresenter([:harold, :richard])
# => [#<WalrusPresenter:0x00000001d773e0 @base_object=:harold>,
     #<WalrusPresenter:0x00000001d773b8 @base_object=:richard>]

WalrusPresenter(WalrusPresenter(WalrusPresenter(:frank)))
# => #<WalrusPresenter:0x00000001d6c760 @base_object=:frank>

答案 1 :(得分:0)

首先,我不认为你想做的事情是超级明智的 - 有可能有更好的方法来实现你想要的。但是如果你真的想通过添加括号处理来创建一个针对类的新运算符,那么下面的代码可以完成我认为的工作。

我正在通过CodeErr从this great blog post复制gnarly类处理代码。

以下是我使用他的代码解决问题的方法:

class Object
  alias_method :orig_method_missing, :method_missing

  def method_missing(m, *a, &b)
    klass = begin
      (self.is_a?(Module) ? self : self.class).const_get(m)
    rescue NameError
    end

    return klass.send(:parens, *a, &b)  if klass.respond_to? :parens
    orig_method_missing m, *a, &b
  end
end

class X
  @@class_arg = {}
  def self.class_arg
    @@class_arg[self.name]
  end
  def self.parens(*args)
    @@class_arg[self.name] = args[0]
    Class.new(self).class_eval do
      define_method :initialize do
        super(*args)
      end
      self
    end
  end

end


class Y < X
end
class Z < X
end

Z(:bubbles)
Y(:frank)
puts Z.class_arg # "bubbles"
puts Y.class_arg # "frank"

答案 2 :(得分:0)

我的解决方案:

class BasePresenter
  attr_accessor :base_object

  def initialize(base_object)
    self.base_object = base_object
  end

  Object.class_eval do
    def BasePresenter base_object
        BasePresenter.new base_object
    end
  end

  def self.inherited child_klass
    Object.class_eval do
           define_method child_klass.to_s.to_sym do |base_object|
               child_klass.new base_object
        end
    end
  end

end

class WalrusPresenter < BasePresenter
end

class Test < WalrusPresenter; end

puts BasePresenter(:bubbles)
puts WalrusPresenter(:frank)
puts Test(:test)