如何实现DSL以通过方法链动态创建新方法?

发布于 2025-02-07 09:37:20 字数 1588 浏览 2 评论 0原文

我有一个类别和该类的实例:

class Thing
   def initialize
      @name = name
   end
end

a = Thing.new("a")

我想通过像这样的调用DSL来动态创建方法并设置其返回值:

如果我写a.is_a.person

  • 。 创建方法
  • ?应为a A.person

?应该返回true如果我写a.is_not_a.man

  • 创建方法
  • 应该为a a.man?

a man ? 写a.is_the.parent_of.joe

  • a parent_of应该为a
  • a.parent_of应该返回<<应该返回<<代码> joe

我尝试执行此操作

class Thing
  attr_reader :name

  def initialize(name)
    @name = name
  end

  def is_a
    Class.new do
      def initialize base
        @base = base
      end
      
      def method_missing name
        @base.define_singleton_method "#{name}?" do
            true
        end
      end
    end.new self
  end

  def is_not_a
    Class.new do
      def initialize base
        @base = base
      end
      
      def method_missing name
        @base.define_singleton_method "#{name}?" do
          false
        end
      end
    end.new self
  end
end

,它适用于is_ais_not_a,如上所述:

jane = Thing.new('Jane')

jane.is_a.person
jane.person? #=> true

jane.is_a.woman
jane.woman? #=> true

jane.is_not_a.man
jane.man? #=> false

但是对于jane.is_is_the..parent_of.joe.joe.joe < /code>,就像更深层的链接一样,我无法弄清楚如何实现它。

我该如何处理?

I have a class and an instance of that class:

class Thing
   def initialize
      @name = name
   end
end

a = Thing.new("a")

I want to dynamically create methods and set their return values by calling a DSL like this:

If I write a.is_a.person:

  • a person? method should be created for a
  • a.person? should return true

If I write a.is_not_a.man

  • a man? method should be created for a
  • a.man? should return false

If I write a.is_the.parent_of.joe

  • a parent_of method should be created for a
  • a.parent_of should return joe

I tried doing this

class Thing
  attr_reader :name

  def initialize(name)
    @name = name
  end

  def is_a
    Class.new do
      def initialize base
        @base = base
      end
      
      def method_missing name
        @base.define_singleton_method "#{name}?" do
            true
        end
      end
    end.new self
  end

  def is_not_a
    Class.new do
      def initialize base
        @base = base
      end
      
      def method_missing name
        @base.define_singleton_method "#{name}?" do
          false
        end
      end
    end.new self
  end
end

And it works for is_a and is_not_a as defined above:

jane = Thing.new('Jane')

jane.is_a.person
jane.person? #=> true

jane.is_a.woman
jane.woman? #=> true

jane.is_not_a.man
jane.man? #=> false

But for the jane.is_the.parent_of.joe, like more deep chaining ones, I'm not able to figure out how I can implement it.

How do I approach this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

椒妓 2025-02-14 09:37:20

使用class.new您每次创建一个新的匿名类is_ais_not_a被调用。尽管这有效,但有一个
更轻巧的方法。您可以创建一个辅助器,每次收到消息时都会调用一个给定的块:

class MethodCallback
  def initialize(&block)
    @block = block
  end

  def method_missing(name, *args)
    @block.call(name, *args)
  end
end

示例用法:

m = MethodCallback.new { |name| puts "#{name} was called"}

m.foo
# foo was called

m.bar
# bar was called

使用此助手,可以像这样简化您的课程:

class Thing
  def initialize(name)
    @name = name
  end

  def is_a
    MethodCallback.new do |name|
      define_singleton_method(:"#{name}?") { true }
    end
  end

  def is_not_a
    MethodCallback.new do |name|
      define_singleton_method(:"#{name}?") { false }
    end
  end
end
jane = Thing.new('Jane')

jane.is_a.person
jane.person? #=> true

jane.is_a.woman
jane.woman? #=> true

jane.is_not_a.man
jane.man? #=> false

现在,对于链接一个jane.is_is_is_the.parent_of.joe.joe << /code>,而不是定义新方法右范围,我们必须
调用/返回另一个MethodCallback实例以获取额外的方法链接:

class Thing
  # ...
  
  def is_the
    MethodCallback.new do |name|
      MethodCallback.new do |value|
        define_singleton_method(name) { value.to_s }
      end
    end
  end
end

在上面的代码中,is_the将分配给>名称。,第二个将分配给value
我们创建的方法将被称为name,它将返回value

jane.is_the.parent_of.joe
#           ^^^^^^^^^ ^^^
#           name      value
jane.parent_of #=> 'joe'

另一个:

jane.is_the.major_of.london
jane.major_of #=> 'london'

With Class.new you create a new anonymous class each time is_a or is_not_a is called. Although this works, there's a
much more lightweight approach. You can create a helper that will invoke a given block each time it receives a message:

class MethodCallback
  def initialize(&block)
    @block = block
  end

  def method_missing(name, *args)
    @block.call(name, *args)
  end
end

Example usage:

m = MethodCallback.new { |name| puts "#{name} was called"}

m.foo
# foo was called

m.bar
# bar was called

With this helper, your class can be simplified like this:

class Thing
  def initialize(name)
    @name = name
  end

  def is_a
    MethodCallback.new do |name|
      define_singleton_method(:"#{name}?") { true }
    end
  end

  def is_not_a
    MethodCallback.new do |name|
      define_singleton_method(:"#{name}?") { false }
    end
  end
end
jane = Thing.new('Jane')

jane.is_a.person
jane.person? #=> true

jane.is_a.woman
jane.woman? #=> true

jane.is_not_a.man
jane.man? #=> false

Now, for the chaining one jane.is_the.parent_of.joe, instead of defining a new method right-away, we have to
invoke / return another MethodCallback instance in order to get an extra level of method chaining:

class Thing
  # ...
  
  def is_the
    MethodCallback.new do |name|
      MethodCallback.new do |value|
        define_singleton_method(name) { value.to_s }
      end
    end
  end
end

In the above code, the first method name after is_the will be assigned to name and the second will be assigned to value.
The method we create will be called just like name and it will return value:

jane.is_the.parent_of.joe
#           ^^^^^^^^^ ^^^
#           name      value
jane.parent_of #=> 'joe'

Another one:

jane.is_the.major_of.london
jane.major_of #=> 'london'
瑕疵 2025-02-14 09:37:20

如果您不使用方法链条来定义对象,则有很多替代方案。这是一种使用“构建器”方法的方法。

class Thing

   attr_reader :name
   def self.define(name, &block)
      thing = Thing.new(name)
      thing.instance_eval(&block)
      thing
   end

   def initialize(name)
      @name = name
   end

   def is(str)

      case str
      when /^\s*a\s+(\w+)\s*$/
         define_a($1)
      when /^\s*not\s+a\s+(\w+)\s*$/
         define_not_a($1)
      when /^\s*the\s*(\w+)\s+of\s+(\w+)\s*$/
         define_relationship($1, $2)
      end

   end

   def define_a(str)
      define_singleton_method("#{str}?".to_sym) {true} 
   end

   def define_not_a(str)
      define_singleton_method("#{str}?".to_sym) {false} 
   end

   def define_relationship(relation, to)
      define_singleton_method("#{relation}_of".to_sym) {to} 
   end
end

a = Thing.define("a") do 
   is "a person"
   is "not a man"
   is "the parent of joe"
end

puts a.name       # => a
puts a.person?    # => true
puts a.man?       # => false
puts a.parent_of  # => joe

If you are not stuck on using method chaining to define your object, there are lots of alternatives. Here is one that uses a "builder" approach.

class Thing

   attr_reader :name
   def self.define(name, &block)
      thing = Thing.new(name)
      thing.instance_eval(&block)
      thing
   end

   def initialize(name)
      @name = name
   end

   def is(str)

      case str
      when /^\s*a\s+(\w+)\s*$/
         define_a($1)
      when /^\s*not\s+a\s+(\w+)\s*$/
         define_not_a($1)
      when /^\s*the\s*(\w+)\s+of\s+(\w+)\s*$/
         define_relationship($1, $2)
      end

   end

   def define_a(str)
      define_singleton_method("#{str}?".to_sym) {true} 
   end

   def define_not_a(str)
      define_singleton_method("#{str}?".to_sym) {false} 
   end

   def define_relationship(relation, to)
      define_singleton_method("#{relation}_of".to_sym) {to} 
   end
end

a = Thing.define("a") do 
   is "a person"
   is "not a man"
   is "the parent of joe"
end

puts a.name       # => a
puts a.person?    # => true
puts a.man?       # => false
puts a.parent_of  # => joe
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文