Ruby DSL嵌套构造

时间:2016-11-16 20:03:11

标签: ruby design-patterns dsl design-principles

我使用以下代码来强制执行DSL嵌套构造的上下文。实现相同功能的其他方法有哪些?

def a &block
  p "a"
  def b &block
    p "b"
    def c &block
      p "c"
      instance_eval &block
    end 
    instance_eval &block
    undef :c
  end 
  instance_eval &block 
  undef :b
end 
# Works
a do
  b do
    c do
    end
  end
end

# Doesn't Work 
b do
end
c do
end

Source

1 个答案:

答案 0 :(得分:2)

你问过其他方法,而不是最好的方法。所以这里有一些例子:

示例A

class A
  def initialize
    p "a"
  end

  def b &block
    B.new.instance_eval &block
  end
end

class B
  def initialize
    p "b"
  end

  def c &block
    C.new.instance_eval &block
  end
end

class C
  def initialize
    p "c"
  end
end

def a &block
  A.new.instance_eval &block
end

例B

稍短一些:

def a &block
  p "a"
  A.new.instance_eval &block
end

class A
  def b &block
    p "b"
    B.new.instance_eval &block
  end

  class B
    def c &block
      p "c"
      C.new.instance_eval &block
    end

    class C
    end
  end
end

示例C

如果您不打算为A :: B :: C对象设置d方法:

def a &block
  p "a"
  A.new.instance_eval &block
end

class A
  def b &block
    p "b"
    B.new.instance_eval &block
  end

  class B
    def c &block
      p "c"
      instance_eval &block
    end
  end
end

示例D

这很有趣:

def new_class_and_method(klass_name, next_klass=Object)
  dynamic_klass = Class.new do
    define_method(next_klass.name.downcase){|&block| p next_klass.name.downcase; next_klass.new.instance_eval &block}
  end
  Object.const_set(klass_name, dynamic_klass)
end

new_class_and_method("A", new_class_and_method("B", new_class_and_method("C")))

def a &block
  p "a"
  A.new.instance_eval &block
end

示例E

我敢说这看起来并不坏:

def new_method_and_class(x)
  define_method(x) do |&block|
    p x
    self.class.const_get(x.capitalize).new.instance_eval &block
  end

  self.const_set(x.capitalize, Class.new)
end

["a", "b", "c"].inject(Object){|klass,x| klass.instance_eval{new_method_and_class(x)} }

示例F

更强大一点:

def new_method_and_class(x, parent_klass = Object)
  parent_klass.class_eval do
    define_method(x) do |&block|
      p x
      parent_klass.const_get(x.capitalize).new.instance_eval &block if block
    end
  end

  parent_klass.const_set(x.capitalize, Class.new)
end

["a", "b", "c"].inject(Object){|klass,x| new_method_and_class(x,klass) }

解释

例B

在例B中,我们首先定义:

  • a()方法
  • 一个A类

两者都在main中定义,因为我们希望a()可以直接使用。 a()方法不会期望打印"a"并将块传递给A的实例。

然后是b()方法。我们不希望它从main获得,所以我们在A类中定义它。我们希望继续使用嵌套方法,因此我们定义了一个B类,它也在A中定义.B类实际上是A :: B类。 A :: B#b()方法还打印"b",并将块传递给B的实例。

我们继续A :: B内部的A :: B :: C,就像我们对A :: B和A一样。

示例F

示例F基本上与示例B类似,但是动态编写。

在示例B中,我们在每个步骤中定义了一个x方法和一个X类,结构完全相同。应该可以通过名为new_method_and_class(x)的方法避免代码重复,该方法使用define_methodconst_setClass.new

new_method_and_class("a") # <- Object#a() and A are now defined

a do
  puts self.inspect
end
#=> "a"
#   <A:0x00000000e58bc0>

现在,我们要定义一个b()方法和一个B类,但它们不应该在main中。 new_method_and_class("b")不会这样做。所以我们传递一个名为parent_klass的额外参数,默认为Object:

parent_klass = new_method_and_class("a")
new_method_and_class("b", parent_klass)

a do 
  b do
    puts self.inspect
  end
end

# => "a"
#    "b"
#    <A::B:0x00000000daf368>

b do
  puts "Not defined"
end

# => in `<main>': undefined method `b' for main:Object (NoMethodError)

要定义c方法,我们只需添加另一行:

parent_klass = new_method_and_class("a")
parent_klass = new_method_and_class("b", parent_klass)
parent_klass = new_method_and_class("c", parent_klass)

依旧等等。

为了避免代码重复,我们可以使用inject_ parent作为累加器值:

["a", "b", "c"].inject(Object){|klass,x| new_method_and_class(x,klass) }

奖金 - 示例G

这是来自示例F的修改代码,它使用基本树结构。

# http://stackoverflow.com/questions/40641273/ruby-dsl-nested-constructs/40641743#40641743
def new_method_and_class(x, parent_klass = Object)
  parent_klass.class_eval do
    define_method(x) do |&block|
      p x.to_s
      parent_klass.const_get(x.capitalize).new.instance_eval &block if block
    end
  end

  parent_klass.const_set(x.capitalize, Class.new)
end

def create_dsl(branch,parent_klass = Object)
  case branch
  when Symbol, String
    new_method_and_class(branch,parent_klass)
  when Array
    branch.each do |child|
      create_dsl(child, parent_klass)
    end
  when Hash
    branch.each do |key, value|
      create_dsl(value, new_method_and_class(key,parent_klass))
    end
  end
end

methods_tree = {
  :a => {
    :b => [
      :c,
      :d
    ],
    :e => :f,
    :g => nil
  }
}

create_dsl(methods_tree)

a do 
  b do
    c do
      puts self.inspect
    end

    d do
    end
  end

  e do
    f do
    end
  end

  g do
    puts self.inspect
  end
end

# => 
#   "a"
#   "b"
#   "c"
#   #<A::B::C:0x0000000243dfa8>
#   "d"
#   "e"
#   "f"
#   "g"
#   #<A::G:0x0000000243d918>