`
darkbaby123
  • 浏览: 104081 次
  • 性别: Icon_minigender_1
  • 来自: 武汉
社区版块
存档分类
最新评论

Active Support 源码研究 -- Concern

阅读更多

前言

 

最近都没怎么更新博客,一来没什么时间学习新知识,二来平时积累的感觉还没到质变的程度。既然没时间学一些新东西,就研究一下基础吧。之所以选择ActiveSupport,是因为它是做基础支持工作的,很多都是对Ruby原生对象的hack,对外部的gem依赖较少。我打算挑几个自己感兴趣的模块来分析分析。第一个就是这个Concern模块。

 

虽然Concern只有不到50行代码,也没依赖其他的模块,但还是花了我半天时间才搞清楚它怎么运作的,惭愧……

 

Concern模块是用来解决module和module之间的依赖问题。这里只说大概用法,想进一步了解,请移步 这篇文章

 

 

ActiveSupport::Concern的作用

 

一般来说,要定义一个模块,为了更好的组织类方法和实例方法,以下这种写法几乎成为一种标准了:

 

 

module M
  def self.included(base)
    base.extend ClassMethod
    base.send :include, InstanceMethod
    base.class_eval do
      # 调用base类的方法,一般用来声明式地修改类
      my_attr_reader :name, ;age
    end
  end
  
  module ClassMethods
    def class_method_1; end
  end

  module InstanceMethods
    def instance_method_1; end
  end
end

 

 

上面例子中base就是混入模块的类,class_eval那一段代码中可以调用base类的类方法my_attr_reader。

 

但如果现在我有两个module,M1和M2,M1依赖于M2,而且它们都需要在被混入类C时执行my_attr_reader方法,似乎可以写成下面这样:

 

 

module M2
  def self.included(base)
    base.class_eval do
      my_attr_reader :age
    end
  end
end

module M1
  def self.included(base)
    base.class_eval do
      my_attr_reader :name
    end
  end
  
  include M2
end

class C
  def self.my_attr_reader(*args); end

  include M1
end

 

但实际上,以上代码是错误的,因为M2被混入M1,所以M2的included中的参数base实际上指向的是module M1,不是class C。这样自然调用不了my_attr_reader方法。

 

ActiveSupport::Concern就是用来解决这类问题的,以上写法可以改成:

 

 

require 'rubygems'
require 'active_support'

module M2
  extend ActiveSupport::Concern
  
  included do
    my_attr_reader :age
  end
end

module M1
  extend ActiveSupport::Concern

  include M2
  
  included do
    my_attr_reader :name
  end
end

class C
  def self.my_attr_reader(*args); end

  include M1
end

 

总体来说,ActiveSupport::Concern实际上是通过一些元编程手段,把module M2混入到class C中去了。甚至,如果module M2依赖了其他模块,ActiveSupport::Concern也会递归地把那些module混入到class C中。下面我们来看看它是怎么做的。

 

 

 

源码分析

 

以下源码取自ActiveSupport 3.0.7:

 

 

module ActiveSupport
  module Concern
    def self.extended(base)
      base.instance_variable_set("@_dependencies", [])
    end

    def append_features(base)
      if base.instance_variable_defined?("@_dependencies")
        base.instance_variable_get("@_dependencies") << self
        return false
      else
        return false if base < self
        @_dependencies.each { |dep| base.send(:include, dep) }
        super
        base.extend const_get("ClassMethods") if const_defined?("ClassMethods")
        base.send :include, const_get("InstanceMethods") if const_defined?("InstanceMethods")
        base.class_eval(&@_included_block) if instance_variable_defined?("@_included_block")
      end
    end

    def included(base = nil, &block)
      if base.nil?
        @_included_block = block
      else
        super
      end
    end
  end
end
 

29行代码,三个方法搞定。不得不佩服Rails core team……看看第一个方法 self.extended。它是一个回调方法,会在ActiveSupport::Concern被extend进其他module或class时触发。参数base就是被extend的module或class。

这个方法的作用是,为base类加入一个实例变量 @_dependencies。默认值是空数组。它用来保存这个模块依赖的其他模块的列表。

 

第二个方法 append_features。这也是一个回调方法。它是在一个module被include进入其他module或class时调用的。当一个class(或module) include另一个module时,class会按照include module相反的顺序去调用每个module的 append_features方法。这个方法的默认实现,是把module的变量,实例方法等东西copy到被混入的class中。如果我们重载一个module的append_features,又什么都不做的话,那么这个module就像没有被混入一样。下面是一个例子:

 

 

module M3
  def self.append_features(base)
    puts "call M3 append_features"
    super
  end

  def m3_instance_method; end
end

module M4
  def self.append_features(base)
    puts "call M4 append_features"
  end

  def m4_instance_method; end
end

class C
  include M3, M4
end

# 这时会打印
# call M4 append_features
# call M3 append_features

c = C.new
c.m3_instance_method  # 这个方法成功的混入到C中
c.m4_instance_method  # 对象c没有这个方法
 

ActiveSupport::Concern中的append_feature并不是在Concern模块被include进其他模块时调用的(这个模块只会被extend,而且这个append_features并不是类方法),而是对于extend了ActiveSupport::Concern的模块而言(如module M1),当它被混入类C时,会触发append_features方法。

这个方法的作用是,如果base类(或模块)有@_dependencies列表时,将自己记入base的@_dependencies中,然后直接return(就是不混入base)。如果base类没有@_dependencies列表(这种情况可以肯定base就是最终要混入的class),就循环自己的@_dependencies列表,依次把每个依赖的module混入base。

 

第三个方法 included。很简单,如果有block。就把block存进 @_included_block 变量。然后在append_featuers中传给base.class_eval。没有block。就和普通的included回调方法一样。

 

方法都说完了,但还是有点绕。下面说下整体流程,以上面的module M1,module M2,class C为例子:

 

  1. module M2和module M1都extend了ActiveSupport::Concern。ActiveSupport::Concern的extended方法被调用,为module M2和module M1类加入实例变量@_dependencies。同时修改了它们的append_features和included两个回调方法。
  2. module M2被混入module M1,module M2的append_features被调用,base参数为module M1。因为module M1有@_dependencies,module M2并没有真的被混入,只是被加入到module M1的@_dependencies列表中;
  3. module M1被混入class C,因为class C没有@_dependencies列表,所以M1遍历自己的@_dependencies列表,将module M2混入class C;
  4. 此时module M2的append_features会再次被调用,但base变成了class C。所以module M2会先把自己的@_dependencies列表中的module加入到base(其实没有可加的),然后把调用append_features的默认行为,然后执行included中的block。调用base的方法。最后混入ClassMethods和InstanceMethods两个模块(如果有)
  5. 回到module M1,它处理完@_dependencies后,会调用super方法,执行append_features的默认行为,将自己混入到class C中,然后执行included的block,最后混入ClassMethods和InstanceMethods两个模块。

总结


基本上,ActiveSupport::Concern的思路就是使用append_features回调,去修改module的被include时的默认行为,延迟module被实际include的时机。这个模块中使用了相当一部分元编程方法。有些细节因为比较直观所以没讲,比如用instance_variable_defined?判断@_dependencies变量定义了没有,用instance_variable_get和instance_variable_set来获取和设置实例变量,等等。
这段源码中,还有一部分我没讲,就是源码第12行的 if base < self 。这个判断是说,当base是self的子类时,返回true,否则返回nil。我没有想到什么情况会有base是self的子类的,除非自定义一个类继承自module……有兴趣的可以自己推演下。
1
2
分享到:
评论

相关推荐

    全国卷2019届高三二轮复习基础练习第5天charge-concern(英语).docx

    在高中英语复习中,词汇和语法的掌握是至关重要的,特别是在全国卷的考试中。以下是一些基于给定内容的知识点: 1. **词汇积累**: - **便宜的(inexpensive/affordable)**: 在描述交通方式时,形容词“便宜的”...

    论文研究-面向方面编程中必要语义约束的研究 .pdf

    面向方面编程中必要语义约束的研究,杨剑青,杨宗源,面向方面编程(AOP)可以很好地解决非功能性关注点(Non-Functional Concern)的封装问题,但是目前的AOP技术过于灵活,甚至会不恰当地破��

    yard-activesupport-concern:使用 ActiveSupport 处理模块的 YARD 插件

    YARD ActiveSupport::关注插件 这是一个扩展,它为使用ActiveSupport::Concern (在 Rails 项目中非常频繁)的模块提供支持。 这是此类模块的示例: module M extend ActiveSupport :: Concern included do # @!...

    mongo-c-driver-1.14.0-x86.rar

    ...\mongo-c-driver\include\libmongoc-1.0\mongoc\mongoc-read-concern.h ...\mongo-c-driver\include\libmongoc-1.0\mongoc\mongoc-read-prefs.h ...\mongo-c-driver\include\libmongoc-1.0\mongoc\mongoc-server-...

    To-Tomb-it-May-Concern:一个非常简单的第一人称3D地牢搜寻器,使用LibGdx用Java编写,并带有一些用Python编写的实用程序

    墓葬可能会引起关注 一个用LibGdx编写的非常简单的3D地牢爬虫。 就像您期望的那样,它几乎完全用Java编写。 实际上,唯一不是用Java编写的部分就是一些用于生成资产的实用程序,它们是用Python编写的。...

    k7 SRIO参考例程

    This is a non-concern for Xilinx buffer users. - Virtex-4 4x core may intermittenly train down to 1x mode - Version fixed : v4.4 - CR#467616 / AR#30314 - Modified oplm_pcs_rst_sequence.v file ...

    MongoDB(mongodb-src-r5.0.4.tar.gz)

    MongoDB Community Server(mongodb-src-r5.0.4.tar.gz)源代码 MongoDB是一个基于分布式文件存储的数据库。由C++语言编写。旨在为WEB应用提供可扩展的高性能数据存储解决方案。 MongoDB是一个介于关系数据库和非...

    Layout Concern about Trace, Ground and Via

    PCB布线布局中关于传输线、微带线与带状线的知识涉及电磁学的基础原理以及PCB设计中的实际应用。接下来,我们将详细解读文中提供的信息。 ### 传输线的概念与同轴电缆 传输线是电磁波传播的介质,通常由两个导体...

    An Additional Antecedent of Empathic Concern

    Two experiments examined the role of valuing the welfare of a person in need as an antecedent of empathic concern. Specifically, these experiments explored the relation of such valuing to a well-known...

    高中英语 重点词汇,短语,句子复习总结学案 新人教版必修4 学案.doc

    12. concern oneself with - 关心,关注 13. instead of - 代替,而不是 14. communicate with - 与...交流 15. body language - 身体语言 16. work out - 解决,锻炼 17. lead a … life - 过着...的生活 18. crowd...

    广东英语第一轮复习-必修1-unit-1-单元过关测试题.doc

    2. 关于concern的用法:concern可以用作名词或动词。在题目中,concerned是形容词,表示“关心的”或“有关的”。题目中的例句展示了concern的不同用法: - be concerned with sth. 表示“与……有关”,例句1中是...

    MongoDB(mongodb-src-r5.0.4.zip)

    MongoDB Community Server(mongodb-src-r5.0.4.zip)源代码 MongoDB是一个基于分布式文件存储的数据库。由C++语言编写。旨在为WEB应用提供可扩展的高性能数据存储解决方案。 MongoDB是一个介于关系数据库和非关系...

    高中英语:Unit 1 Friendship Warming up & vocabulary(新人教必修1).doc

    - concern - 关心,担忧,涉及 - outdoors - 在户外 - settle - 安家,定居 - pack - 捆扎,打包 - grateful - 感激的 - communicate - 交流,沟通 - strength - 力量,才干 - entire - 全部的,完整的 - ...

    Spring-data-mongo应用

    &lt;mongo:template id="mongoTemplate" db-factory-ref="mongoDbFactory" write-concern="NORMAL"/&gt; ``` 3. **配置属性文件** 创建 `db.properties` 文件来存储 MongoDB 的连接信息: ```properties mongo...

    高中英语必修4重点词汇,短语,句子复习总结.pdf

    12. concern oneself with - 关注 13. instead of - 而不是 14. communicate with - 与...交流 15. body language - 肢体语言 16. work out - 解决,锻炼 17. lead a ... life - 过着...的生活 18. crowd in - 涌上...

    WISC-R examiner errors: Cause for concern

    WISC-R examiner errors: Cause for concern 78 Janet G. Melancon and Bruce Thompson WICKER, T. E. (1980, Sept.). The effects of cognitive dissonance or disequilibrium on conservation attain- ment ...

    高考英语作文万能模板附范文.doc

    These days, environmental pollution is a pressing concern. Factories continue to emit pollutants, and littering is prevalent. To address this, stricter regulations on industrial emissions could be ...

    spring-data-mongodb-parent-reference

    文档中的 "MongoDB support" 部分提供了如何开始使用MongoDB以及如何连接到MongoDB的具体示例和方法。这部分内容包括使用Java和XML元数据注册MongoDB实例,以及MongoDbFactory接口的使用。此外,还介绍了Mongo...

Global site tag (gtag.js) - Google Analytics