【问题标题】:Ruby - DSL using instance variablesRuby - 使用实例变量的 DSL
【发布时间】:2014-03-14 18:23:47
【问题描述】:

这里的代码是对更大解决方案的简化。我试图弄清楚如何 制作一个可以很好地“读取”的 ruby​​ DSL。第一个代码块;工作(现在工作) 我想知道如何编写 DSL

问题的核心在于,在处理类时,它没有可供我使用的实例变量。即:@match_code

有人知道更简单更优雅的解决方案吗?整个代码必须保存在一个类中。

我希望它看起来像:

class MatchStuff 
  include ProcessBase

  match 'account' do | event |
    pp event
  end
end

matcher = MatchStuff.new
matcher.accept 'account'

当前工作(不是很好)代码

class ProcessBase
  def initialize
    @match_code = []
  end

  def match(string_match, &block)
    @match_code.push([string_match, block])
  end

  def accept(test_str)
    @match_code.each do | test_block |
      if test_str == test_block[0])
        test_block[1].call test
      end
    end
  end
end

class MatchStuff < ProcessBase
  def initialize 
    super

    match 'account' do | event |
      pp event
    end
  end
end

test = MatchStuff.new
test.accept 'account'

【问题讨论】:

    标签: ruby metaprogramming dsl


    【解决方案1】:

    为了在class MatchStuff 的主体中使用来自ProcessBase 的方法,您可以在extend 之外添加include 或对其进行子类化:

    class MatchStuff
      include ProcessBase
      extend ProcessBase
    
      match 'account' do |event|
        pp event
      end
    end
    

    但是你会遇到@match_code 引用match 中的类实例变量,但accept 中的常规实例变量的问题。我会在ProcessBase 中添加一个读取器方法并在accept 中的类上使用它,因此您始终使用类实例变量。然后你可以使用||= 来避免需要initialize 方法(当你extend 时不会为类调用)。

    module ProcessBase
      # reader method, autovivifying to empty Array
      def match_code
        @match_code ||= []
      end
    
      def match(string_match, &block)
        # using the reader here, on self, which is the class
        match_code.push([string_match, block])
      end
    
      def accept(test_str)
        # here, self is the instance, so we have to explicitly get the class
        # and access match_code from there
        self.class.match_code.each do | test_block |
          if test_str == test_block[0])
            test_block[1].call test
          end
        end
      end
    end
    

    或者,您可以将match(带有match_code阅读器)和accept放在单独的模块中,extend一个和include另一个。这样做的好处是不会为您的实例提供不起作用的match 方法(因为它使用了错误的match_code)。您甚至可以定义一个 includedextended 方法,当您执行另一个时执行一个。

    module ProcessClassBase
      def match_code
        # as above
      end
    
      def match
        # as above
      end
    end
    
    module ProcessInstanceBase
      def accept
        # as above
      end
    
      def included(other_mod)
        other_mod.extend(ProcessClassBase)
      end
    end
    
    class MatchStuff
      include ProcessInstanceBase # now this also extends ProcessClassBase
    
      match 'account' do |event|
        pp event
      end
    end
    

    值得注意的是,如果您将MatchStuff 子类化,这一切都会中断。当调用accept 时,子类的实例(称为MatchStuffSub)将尝试访问MatchStuffSub.match_code 而不是MatchStuff.match_codematch 放置东西的地方)。

    【讨论】:

    • 谢谢!我没想到将代码存储在类变量中!
    【解决方案2】:

    MatchStuff 定义为

    class MatchStuff < ProcessBase
       def set_match
          match 'account' do |event|
            pp event
          end
       end
    end
    

    并从ProcessBase 构造函数调用set_match

    class ProcessBase
      def initialize
        @match_code = []
        set_match # Must be defined in derived class
      end
      ...
    end
    

    由于您没有为 MatchStuff 指定构造函数,它会自动调用父类构造函数。

    这实现了我对您的目标的解释,即以尽可能少的方式在MatchStuff 中指定'account' 字符串和相关块。

    【讨论】:

      【解决方案3】:

      您可以尝试不使用实例变量。目前尚不清楚匹配字符串的可能值是什么样的,因此我举了一个示例,说明您可能希望如何使它们对这种方法“安全”。

      module ProcessBase
        def self.included(c)
          c.extend(ClassMethods)
        end
      
        module ClassMethods
          def match(string_match, &block)
            method = string_match.downcase.gsub(/\s+/,'_').to_sym
            define_method(method, &block)
          end
        end
      
        def accept(string_match)
          method = string_match.downcase.gsub(/\s+/,'_').to_sym
          self.send(method, 'test')
        end
      end
      
      class MatchStuff
        include ProcessBase
      
        match 'account' do | event |
          pp event
        end
      end
      
      test = MatchStuff.new
      test.accept 'account'
      

      【讨论】:

      • string_match 会出现多次。我正在寻找类似于具有多个听众的发布/订阅系统的东西。以上响应,只允许一个。
      猜你喜欢
      • 2014-12-26
      • 2014-09-21
      • 2014-07-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-07-29
      • 2013-03-24
      相关资源
      最近更新 更多