【问题标题】:How can I convert this code to meta-programming, so I can stop duplicating it?如何将此代码转换为元编程,这样我就可以停止复制它?
【发布时间】:2010-02-17 01:30:50
【问题描述】:

我已经为 building .net systems with ruby / rake 准备了一个小但不断增长的框架,我已经研究了一段时间。在这个代码库中,我有以下内容:

require 'rake/tasklib'

def assemblyinfo(name=:assemblyinfo, *args, &block)
  Albacore::AssemblyInfoTask.new(name, *args, &block)
end

module Albacore
  class AssemblyInfoTask < Albacore::AlbacoreTask
    def execute(name)
      asm = AssemblyInfo.new
      asm.load_config_by_task_name(name)
      call_task_block(asm)
      asm.write
      fail if asm.failed
    end
  end
end

此代码遵循的模式在框架中重复了大约 20 次。每个版本的区别在于正在创建/调用的类的名称(而不是 AssemblyInfoTask,它可能是 MSBuildTask 或 NUnitTask),以及执行方法的内容。每个任务都有自己的执行方法实现。

我一直在修复这种代码模式中的错误,每次需要修复时,我都必须重复修复 20 次。

我知道可以做一些元编程魔术,并从一个位置为我的每项任务连接这段代码......但我很难让它工作。

我的想法是我希望能够这样调用:

create_task :assemblyinfo do |name|
  asm = AssemblyInfo.new
  asm.load_config_by_task_name(name)
  call_task_block(asm)
  asm.write
  fail if asm.failed
end

这将连接我需要的一切。

我需要帮助!提示、建议、愿意解决这个问题的人……我怎样才能避免一遍又一遍地重复这种代码模式?

更新:你可以在这里获得完整的源代码:http://github.com/derickbailey/Albacore/提供的代码是/lib/rake/assemblyinfotask.rb

【问题讨论】:

  • +1 使用 Github,很高兴能够浏览整个源代码。
  • 为什么不把所有的类定义放到模块文件中呢?然后,元编程方法只会为您节省 1 行真正的(非“结束”)代码,同时以额外的混淆为代价。除非您需要动态定义任务,否则我只会使用 vanilla 方法。
  • 。 . .当然,除非这更像是一个学习练习而不是一个完成的项目。

标签: ruby metaprogramming dry


【解决方案1】:

好的,这里有一些元编程可以满足您的需求(在 ruby​​18 或 ruby​​19 中)

def create_task(taskname, &execute_body)
  taskclass = :"#{taskname}Task"
  taskmethod = taskname.to_s.downcase.to_sym
  # open up the metaclass for main
  (class << self; self; end).class_eval do
    # can't pass a default to a block parameter in ruby18
    define_method(taskmethod) do |*args, &block|
      # set default name if none given
      args << taskmethod if args.empty?
      Albacore.const_get(taskclass).new(*args, &block)
    end
  end
  Albacore.const_set(taskclass, Class.new(Albacore::AlbacoreTask) do
    define_method(:execute, &execute_body)
  end)
end

create_task :AssemblyInfo do |name|
  asm = AssemblyInfo.new
  asm.load_config_by_task_name(name)
  call_task_block(asm)
  asm.write
  fail if asm.failed
end

元程序员工具箱中的关键工具有:

  • class&lt;&lt;self;self;end - 获取任何对象的元类,因此您可以在该对象上定义方法
  • define_method - 这样您就可以使用当前局部变量定义方法

也很有用

  • const_set, const_get: 允许你设置/获取常量
  • class_eval :允许您使用 def 定义方法,就像您在 class &lt;Classname&gt; ... end 区域中一样

【讨论】:

  • 如果您想要自动大写等功能,您可以在 ActiveSupport 中找到该功能。
  • 我正在使用 Ruby 1.8.6... 我认为这个解决方案适用于 1.9+
  • ActiveSupport 只能在您使用下划线分隔符时自动大写,这似乎是 Derick 不想要的。
  • 这几乎是完美的......元类有问题,并且 *args 和 &block 仍然不符合 1.8.6,但我能够通过将方法定义更改为 Object 来解决这个问题。 class_eval(FILE, LINE) def #{taskmethod}(name, *args, &block) Albacore.const_get("#{taskclass}")。 new(name, *args, &block) end EOF 效果很好!感谢您帮助我做这件事。我肯定为我的元腰带挑选了一些新工具。 :)
【解决方案2】:

类似这样的东西,在 ruby​​ 1.8.6 上测试过:

class String
  def camelize
    self.split(/[^a-z0-9]/i).map{|w| w.capitalize}.join
  end
end

class AlbacoreTask; end

def create_task(name, &block)
  klass = Class.new AlbacoreTask
  klass.send :define_method, :execute, &block
  Object.const_set "#{name.to_s.camelize}Task", klass
end

create_task :test do |name|
  puts "test: #{name}"
end

testing = TestTask.new
testing.execute 'me'

核心部分是“create_task”方法,它:

  • 创建新类
  • 添加执行方法
  • 为类命名并公开它

【讨论】:

    猜你喜欢
    • 2015-04-26
    • 1970-01-01
    • 2017-10-04
    • 2016-05-01
    • 2019-11-29
    • 2012-07-01
    • 1970-01-01
    • 2020-10-24
    • 1970-01-01
    相关资源
    最近更新 更多