自己写一个迷你的DSL

2017-04-02  本文已影响92人  33d8e4ec2cc9

我想要达到的效果是这样的

class Foo
  redis_cache 10 do
    def foo
      'bar'
    end

    def bar
      'foo'
    end
  end
end

被包裹在redis_cache里面的方法会在第一次执行后存入redis, 在第二次执行该方法的时候,去redis里面取,并且根据传入的秒数来设定缓存周期,以起到缓存的效果,老实讲是没什么必要这么弄,但是你们难道对如何实现不感兴趣吗?

感兴趣的同学往下看

思路

我们首先要熟悉ruby祖先链相关的知识,当一个实例执行一个方法的时候,是怎么样的一个流程,实例对象中没有方法,只有变量,实例执行方法的时候,先向右一步,到自己的类中找,假如没有找到,会继续往上,到该类的超类里面找,直到Object为止,还没找到的话,从一开始的类开始找名叫method_missing的方法,假如还是没有,会在Object这里找到一个内置的method_missing,这个方法没什么内容,就是抛出一个undifine_method xxx for xxx。。。
不过这一期的内容用不到这些知识

你们有没有想过,当一个类混入一个模块(include Foo)后,类里面的实例方法,和模块中同名的实例方法,哪个会被优先调用,答案是,优先执行类里面的,再是模块里面的,这个可以自己去做个试验,看到这里,如何实现这个简单的DSL就很明朗了,我们首先把redis_cache里面的block,拿到后跑到该类的超类中写一个方法,然后再在自己的类中建一个同名方法,用super来实现上下层的方法的交互,但是我们写进超类里面会有一个问题,方法有可能会被写进Object,这样会产生一个问题,其他的任何类都会拥有这个方法,这不是我们想要的效果,那利用我们刚说的第二点,把方法写进一个模块,然后include,就可以了

初步的代码是这样的

class Object
  def redis_cache seconds=300, &block
    top_mod = self.const_set(:TopMethods, Module.new)
    top_mod.module_eval &block
    include top_mod

    top_mod.instance_methods.each do |m|
      self.class_eval <<-CODE
        def #{m}(*args)
          puts 'in bottom'
          super
        end
      CODE
    end
  end
end

class Foo
  redis_cache do
    def bar
      'it works!'
    end
  end
end

aa = Foo.new
aa.bar
#=> in bottom
#=> 'it works!'

后面如何继续实现,相信大家都有思路了

上一篇下一篇

猜你喜欢

热点阅读