首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >进入Ruby元编程:为多个内部方法生成代理方法

进入Ruby元编程:为多个内部方法生成代理方法
EN

Stack Overflow用户
提问于 2010-06-14 19:27:35
回答 1查看 907关注 0票数 1

我多次听到Ruby吹嘘它的超壮观的元编程功能,我想知道是否有人能帮助我开始解决这个问题。

我有一个类,作为一个“归档”类型,内部方法根据输入处理和输出数据。但是,为了性能目的,类本身的存档中的项是用整数表示和处理的。存档之外的实际项是通过字符串表示形式知道的,它只是number_representation.to_s(36)。

正因为如此,我用“代理方法”将每个内部方法连接起来,该方法将输入转换为归档识别的整数形式,运行内部方法,并将输出(单个其他项或它们的集合)转换回字符串。

命名约定如下:内部方法由_method_name表示;它们对应的代理方法由method_name表示,没有前导下划线。

例如:

代码语言:javascript
复制
class Archive

  ## PROXY METHODS ##
  ## input: string representation of id's
  ## output: string representation of id's

  def do_something_with id
    result = _do_something_with id.to_i(36)
    return nil if result == nil
    return result.to_s(36)
  end

  def do_something_with_pair id_1,id_2
    result = _do_something_with_pair id_1.to_i(36), id_2.to_i(36)
    return nil if result == nil
    return result.to_s(36)
  end

  def do_something_with_these ids
    result = _do_something_with_these ids.map { |n| n.to_i(36) }
    return nil if result == nil
    return result.to_s(36)
  end

  def get_many_from id
    result = _get_many_from id
    return nil if result == nil         # no sparse arrays returned
    return result.map { |n| n.to_s(36) }
  end

  ## INTERNAL METHODS ##
  ## input: integer representation of id's
  ## output: integer representation of id's

  private

  def _do_something_with id
    # does something with one integer-represented id,
    # returning an id represented as an integer
  end

  def do_something_with_pair id_1,id_2
    # does something with two integer-represented id's,
    # returning an id represented as an integer
  end

  def _do_something_with_these ids
    # does something with multiple integer ids,
    # returning an id represented as an integer
  end

  def _get_many_from id
    # does something with one integer-represented id,
    # returns a collection of id's represented as integers
  end
end

如果id.class ==字符串位于内部方法的开头,则有几个原因不能将它们转换:

这些内部方法在某种程度上是计算密集型的递归函数,我不希望在每个步骤中多次检查的开销,除非添加额外的参数,否则无法判断是否在最后重新转换,我想把这看作是理解ruby meta-programming的一个练习。

有人有什么想法吗?

编辑

我想要的解决方案最好能够获得一个方法名数组

代码语言:javascript
复制
@@PROXY_METHODS = [:do_something_with, :do_something_with_pair,
                   :do_something_with_these, :get_many_from]

迭代它们,在每次迭代中,输出代理方法。我不知道如何处理这些参数,但是有没有方法来测试一个方法的参数呢?如果没有,那么简单的鸭子类型/类似概念也可以。

我已经想出了自己的解决方案,使用#class_eval

代码语言:javascript
复制
@@PROXY_METHODS.each do |proxy|
  class_eval %{ def #{proxy} *args
                  args.map! do |a|
                    if a.class == String
                      a.to_i(36)
                    else
                      a.map { |id| id.to_i(36) }
                    end
                  end
                  result = _#{proxy}(*args)

                  result and if result.respond_to?(:each)
                               result.map { |r| r.to_s(36) }
                             else
                               result.to_s(36)
                             end
                end
              }
end

然而,#class_eval似乎是bit...messy吗?或者与“应该”相比不雅致。

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2010-06-14 19:51:27

代码语言:javascript
复制
class Archive
  # define a new method-creating method for Archive by opening the
  # singleton class for Archive
  class << Archive
    private # (make it private so no one can call Archive.def_api_method)
    def def_api_method name, &defn
      define_method(name) do |*args|
        # map the arguments to their integer equivalents,
        # and pass them to the method definition
        res = defn[ *args.map { |a| a.to_i(36) } ]
        # if we got back a non-nil response, 
        res and if res.respond_to?(:each)
                  # map all of the results if many returned
                  res.map { |r| r.to_s(36) } 
                else
                  # map the only result if only one returned
                  res.to_s(36)
                end
      end
    end
  end
  def_api_method("do_something_with"){ |id| _do_something_with(id) }
  def_api_method("do_something_with_pair"){ |id_1, id_2| _do_something_with_pair id_1.to_i(36), id_2.to_i(36) }
  #...
end

与打开单例来定义Archive.def_api_method不同,您可以简单地使用

代码语言:javascript
复制
class Archive
  def Archive.def_api_method
    #...

但是我没有这样做的原因是,任何能够访问Archive类的人都可以使用Archive.def_api_method调用它。打开单例类允许我将def_api_method标记为私有,因此只能在self == Archive时调用它。

如果您总是使用相同(或可派生)的名称调用内部版本,那么可以使用#send直接调用它(而不是传递定义块)。

代码语言:javascript
复制
class Archive
  # define a method-creating method that wraps an internal method for external use
  class << Archive
    private # (make it private so no one can call Archive.api_method)
    def api_method private_name
      public_name = private_name.to_s.sub(/^_/,'').to_sym
      define_method(public_name) do |*args|
        # map the arguments to their integer equivalents,
        # and pass them to the private method
        res = self.send(private_name, *args.map { |a| a.to_i(36) })
        # if we got back a non-nil response, 
        res and if res.respond_to?(:each)
                  # map all of the results if many returned
                  res.map { |r| r.to_s(36) } 
                else
                  # map the only result if only one returned
                  res.to_s(36)
                end          end
      # make sure the public method is publicly available
      public public_name
    end
  end

  api_method :_do_something_with
  api_method :_do_something_with_pair

  private

  def _do_something_with
    #...
  end
  def _do_something_with_pair
    #...
  end
end

这更像是其他元方法(如attr_readerattr_writer )所做的事情。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/3040263

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档