首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 开发语言 > 编程 >

项目中Delegator的应用来实现偷天换日的能力

2012-10-27 
项目中Delegator的运用来实现偷天换日的能力Delegator中文名可以叫托管,委托.在JAVA中是一种比较高深的设

项目中Delegator的运用来实现偷天换日的能力
Delegator中文名可以叫托管,委托.
在JAVA中是一种比较高深的设计模式.跟继承的思想有一点点像,但远比继承来的灵活.
简单来理解,可以与现实世界来类比,你交给另一个类帮你打点点事件,有点像助理.
这个助理可以帮几个人同时打点事件.也可以自己额外做些事件.

今天在项目中,遇到了一个类似的问题:

以前的代码:

o = M1::M2::Klass.newo.method( :a=> 1 )o.method( :a=> 2 )


要求在尽可能不改动原有代码的基础上,增加功能:
1. 检查每次调用的方法的参数是否符合它的原有代码的注释
注释类似于(非常规范严格)
#id=>a,name=>参数,type=>s,must=>true,default=>"",value=>"{text}",descrip=>"ok"

这个注释是说方法必须具有 hash[:a]这个参数,如果没有就应当报错.

乍一看,不改变表现代码就增加功能? 怎么可能嘛....
No,由于开发使用的ruby,并且采用的惯例配置,使这个需求成为可能.
我们的项目惯例如下:
所有的类名与自己所在的文件名一致,目录与所在的模块一致.
即:
M1:M2:Klass 所在文件为 /m1/m2/klass.rb

以下看如何实现:

首先,由于M1::M2::Klass本身设置为按需加载(如何实现?)
代码如下:
class Module  def const_missing(name)    name = self.name.to_s + "::" + name.to_s    exception = NameError.new("uninitialized constant #{name}")    begin      #require File.join(ATT::ConfigureManager.root,'keywords','keyword',name.underscore)      load_module(name.to_s)    rescue ATT::Exceptions::LoadError      raise $!    rescue ATT::Exceptions::NotFoundError,Exception      raise exception    end  endendclass Object  def self.const_missing(name)    exception = NameError.new("uninitialized constant #{name}")    begin      load_module(name.to_s)    rescue ATT::Exceptions::LoadError      raise $!    rescue ATT::Exceptions::NotFoundError,Exception      raise exception    end  endend

简单解决下,利用Module#const_missing与Object.const_missing, 当ruby解释器在找不到定义的类或模块时,会调用两个方法,你可以重新定义使得自动加载成为现实.当然有人会说用AutoLoad啊,那我问你,用autoload不是要自己写额外代码吗? 况且这里的load_module实现了更多功能,如相关依赖的加载,模块的自动关联等.由于这里讨论与此无关,不说load_module的细节了.
好了,第一步已经完成.
那么,如何才能进行委托呢?
第二步,偷天换日,load_module并不实际返回要加载的对象,而是返回它的委托对象.即
M1::M2::Klass.new

返回的不是Klass对象,而是一个实例化的KlassProxy, 见核心实现:
def load_module( klass_str )  #前面忽略大量关联加载的代码  #生成Klass  instance = eval(klass_str)  #转给委托类  keyword_proxy_for_class_name( instance )enddef keyword_proxy_for_class_name( klass ) KeywordProxy.new( klass ) do |m,hash| #处理委托回调代码,这里省略 endend

第二步后,换日已经成功,一切只差委托的实现了.
第三步,委托类,请提供帮忙额外检查方法参数
由于ruby内置了Delegator,那么我们自定义实现太简单了,看:
  class KeywordProxy    def self.proxy( klass,&block ) # :yields: hash      ret_obj = KeywordProxyBase.new( klass.new )      ret_obj.before_call(&block)      ret_obj    end    class << self      alias __proxy__ proxy    end    #对外的委托接口,设置要委托的对象,以及要委托的方法执行前置代码.    def initialize( klass, &block )      @klass = klass      @block = block      class <<self        #这里采用匿名方法扩展,使实例化的keyword的类依然支持new,对外代码极其自然        def new          KeywordProxy.proxy(@klass, &@block)        end      end    end  end  class KeywordProxyBase < Delegator    def initialize( obj )      @__obj__ = obj      #这里不要复制委托的办法,只需回传即可,所以设置为nil      super(nil)      @before_call_blocks = []      @after_call_blocks = []    end        def __getobj__      @__obj__    end        def __setobj__(obj)      @__obj__ = obj    end            def before_call(&block)      if block        @before_call_blocks << block      end    end        def after_call(&block)      if block        @after_call_blocks << block      end    end        def method_missing(m, *args)      # 这里才是真正的回调,某一个方法要执行时,要帮助它执行回调.然后才给它.      @before_call_blocks.each do |block|        block.call m,*args      end      ret = super(m,*args)      @after_call_blocks.each do |block|        block.call ret      end      ret    end      end


好了,4个步骤一完,至此整个需求完成了.
我们实现了 "不改变任何表现代码的情况下,偷天换日的实现了往实际调用方法中添加自己的回调." 这种实现不亚于rails任何的官方代码,虽然有些过度利用ruby的动态表达能力,但极少的影响用户接口,简洁至极.
我想,如果你对这部分代码毫无疑问的理解的话,内置的Delegator类就非常easy看懂了.所以这里就不要复述了,接下来再找点时间简单看一下Delegator本身的实现吧.
不错 target = self.__getobj__ unless target.respond_to?(m) super(m, *args) end target = self.__getobj__ unless target.respond_to?(m) super(m, *args) end

没有必要的,实际上我们KeywordProxyBase 继承了Delegator了, 而
method_missing 中的super已经调用了你提出的代码了.
4 楼 orcl_zhang 2011-10-15   在引用@before_call_blocks.each do |block| 
      block.call m,*args 
    end 
的时候,如果找不到方法,会调用target的method_missing,而不是Delegator的。 5 楼 ruby_windy 2011-10-18   orcl_zhang 写道在引用@before_call_blocks.each do |block| 
      block.call m,*args 
    end 
的时候,如果找不到方法,会调用target的method_missing,而不是Delegator的。

嗯,想的很周全,@orcl_zhang, 一般我们在block中不会产生异常,也不应关联代理类的内部方法,所以之前我没有考虑这点~

关于这个,后来我在实施中还是发现了一点点问题,希望有可能用到的同学注意下:

第一次new的对象是ok的,正是KeywordProxy,
但第二次由于已经在load_module中被加载,返回最终的结果.
那我们应当这么做:

使用 keyword_proxy_for_class_name( klass )来处理即可.
如果稍长,可以定义别名:
alias k keyword_proxy_for_class_name
然后使用 k(klass) 来获取代理对象:)

热点排行