`
ruby_windy
  • 浏览: 62742 次
  • 性别: Icon_minigender_1
  • 来自: 深圳
社区版块
存档分类
最新评论

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

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

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

以前的代码:
o = M1::M2::Klass.new
o.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
  end
end


class 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
  end
end

简单解决下,利用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 )
end
def keyword_proxy_for_class_name( klass )
 KeywordProxy.new( klass ) do |m,hash|
 #处理委托回调代码,这里省略
 end
end

第二步后,换日已经成功,一切只差委托的实现了.
第三步,委托类,请提供帮忙额外检查方法参数
由于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本身的实现吧.
2
1
分享到:
评论
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) 来获取代理对象:)
4 楼 orcl_zhang 2011-10-15  
引用
@before_call_blocks.each do |block| 
      block.call m,*args 
    end 

的时候,如果找不到方法,会调用target的method_missing,而不是Delegator的。
3 楼 ruby_windy 2011-10-14  
orcl_zhang 写道
method_missing需要稍微修改下,
在before_call_blocks前面加上一句:
    target = self.__getobj__
    unless target.respond_to?(m)
      super(m, *args)
    end


没有必要的,实际上我们KeywordProxyBase 继承了Delegator了, 而
method_missing 中的super已经调用了你提出的代码了.
2 楼 orcl_zhang 2011-10-14  
method_missing需要稍微修改下,
在before_call_blocks前面加上一句:
    target = self.__getobj__
    unless target.respond_to?(m)
      super(m, *args)
    end
1 楼 orcl_zhang 2011-10-14  
不错

相关推荐

    Python库 | delegator.py-0.0.1.tar.gz

    综上所述,`delegator.py`是一个实用的Python库,为开发者提供了方便的方式来管理子进程和执行命令行工具,尤其适合那些需要与系统命令进行交互的项目。通过学习和使用`delegator.py`,可以提高代码的可读性和可维护...

    Python库 | delegator.py-0.0.13.tar.gz

    资源分类:Python库 所属语言:Python 资源全名:delegator.py-0.0.13.tar.gz 资源来源:官方 安装方法:https://lanzao.blog.csdn.net/article/details/101784059

    PyPI 官网下载 | delegator.py-0.0.6.tar.gz

    在压缩包子文件的文件名称列表中只有一个条目:`delegator.py-0.0.6`,这通常包括`setup.py`(用于安装库的Python脚本)、`README`(包含项目说明和使用指南)、`LICENSE`(许可协议)以及`delegator.py`本身(库的...

    ghcjs-dom-delegator:ghcjs 的 dom-delegator 绑定

    3. **虚拟 DOM**: 虚拟 DOM 是一种内存中的数据结构,用来表示实际的 DOM 树,通过比较虚拟 DOM 和实际 DOM 的差异来实现高效的更新,减少了对实际 DOM 的操作。 4. **事件处理**: 在 JavaScript 中,事件处理包括...

    dom-delegator, 使用委托事件装饰元素.zip

    dom-delegator, 使用委托事件装饰元素 dom代理使用委托事件装饰元素dom-delegator 允许你将 EventHandler 附加到dom元素。当发生正确类型的事件时,dom-delegator 将调用你的EventHandler这允许你从事件编写器分离...

    delegator:微包,用于定义委托的方法和属性

    它与Ruby中的委托语法非常相似。 delegate类上的委托属性可以是序列或单个字符串,其参数由空格分隔(类似于namedtuple )。 Delegatee属性必须是第一个参数。 如果您不喜欢这种样式-不用担心-有三种可互换的表示...

    angular-delegator:编写更小、更干净的 AngularJS 服务

    请注意: Angular Delegator 是一个非常年轻的项目。 API 可能会在不久的将来发生变化。 例子 如果您有一个大型验证服务来检查帐户的名称、日期、地址和付款信息,您可以将其分解如下: angular . module ( '...

    dumb_delegator:Ruby的stdlib中的Delegator和SimpleDelegator很有用,但它们引入了大多数Kernel。 这不适用于许多用途。 例如,委托给Rails模型

    使用Delegator或SimpleDelegator时,不会转发这些方法。 require "delegate"class MyAwesomeClass # ...endo = MyAwesomeClass . newd = SimpleDelegator . new ( o )d . class #=&gt; SimpleDelegatord . is_a? ...

    Python-SubprocessesforHumans20

    4. **异步支持**:除了同步执行命令,`delegator.py`还支持异步执行,通过`delegator.spawn()`方法,可以轻松实现非阻塞的子进程执行。 5. **日志记录**:库内置了日志记录功能,可以在执行过程中捕获和记录输出,这...

    Delegator:一个不错的API,适用于Laravel,可帮助您保持响应状态

    "require-dev" : { "by-cedric/delegator" : "dev-master"} 委托人扩展了Laravel的基本Response外观。 因此,您需要使用Response外观的Delegator版本。 当然,您仍然可以使用Response的标准功能。 在您的config / ...

    Laravel开发-presenter

    在Laravel框架中,Presenter(演示者)是一种设计模式,用于将复杂的业务逻辑与视图层解耦。这种模式在Laravel 5中通常被...在实际项目开发中,合理运用Presenter能帮助我们构建更加整洁、易于理解和测试的代码结构。

    redux-delegator:以结构化方式组成redux reducer

    #redux-delegator 以结构化方式组成减速器 const companiesReducer = handleActions({ ADD_COMPANY: (state, {payload:{doc, ASIN}}) =&gt; state.set(ASIN, doc), REMOVE_COMPANY: (state, {payload:{ASIN}}) =&gt; ...

    Windows下采用IOCP实现的ACE的Proactor框架剖析

    在Windows环境下,ACE的Proactor框架利用IOCP来实现高效的异步I/O操作。它提供了一种统一的接口,使得开发者可以在不同平台上使用相同的代码处理异步I/O,无需关心底层实现的细节。 **ACE Proactor框架的核心组件**...

    NC参照联动代码例子

    总的来说,这段代码展示了在NC(可能指的是NetEase Cloud或类似系统)环境中实现参照联动和权限控制的一个实例。通过`setDefaultConditionClient`、`initQueryConditionDLG`和`initRef`等方法,实现了根据用户的选择...

    委托模式详细讲解(委托)

    - 使用明确的命名约定,确保Delegator和Delegate之间的关系一目了然,如命名示例中的WidgetFactory。 - 添加清晰的注释,解释为何使用委托模式,以及各个组件的具体职责,有助于代码审查和后续维护。 - 考虑采用设计...

    QT_MVC.zip_MVC QT_qt MVC_site:www.pudn.com

    在Qt中,MVC模式被扩展为一个更强大的模型-视图-委托(Model-View-Delegator)架构,有时也称为MVVM(Model-View-ViewModel)。在这个架构中,"模型"(Model)负责管理应用程序的核心数据;"视图"(View)负责显示...

    用PHP的反射实现委托模式的讲解

    委托模式是软件开发中的一种设计模式,它允许一个对象将一些职责委派给另一个对象来执行。在PHP中,可以利用反射机制来实现动态的委托模式,从而在运行时动态地处理方法调用和行为分发。 委托模式主要包含两个角色...

    Ofbiz源码阅读笔记 之 请求控制篇

    ### Ofbiz源码阅读笔记之请求控制篇 #### 一、引言 本文档基于Ofbiz 10.04.02版本进行解读,...理解这些细节对于开发者来说至关重要,不仅可以帮助他们更好地利用Ofbiz框架,还能为未来的项目开发提供有力的支持。

Global site tag (gtag.js) - Google Analytics