类引入
ruby 中module
用于替代多继承的功能,是一个可以包含常量、方法、类定义以及其他模块的集合。
module
除了不可以new
实例,以及没有Class
类中一些特殊的方法和class
没什么区别。
其定义语法如下:
复制 module ModuleA
constA
def method1
... do something
end
end
Mix-in 可以将多个模块混合到类中,即通过include
在类中引入模块,以获得模块的方法、常量等。
复制 class ClassA
include ModuleA
include ModuleB
...
end
class ClassB
include ModuleA
include ModuleB
...
end
每个模块具有单独的命名空间,以保证模块下的常量和方法互不影响。
Const
在类中引入模块后,其中的常量都被引入到了所在类中,当然也可以直接通过模型的命名空间直接访问模型的常量。
复制 module ModuleA
constA = 'a'
end
class ClassA
p ModuleA :: constA # => "a"
include ModuleA # 引入模块
p constA # => "a" # 直接在类中使用模块中的常量
end
Method
在类中引入模块后,其中的方法可以被该类的实例调用。
同时也可以在模块中将方法定义为模块方法。
复制 module ModuleA
def method1
p "do method1.."
end
def method2
p "do method2.."
end
module_function :method2 # 将方法2设置为模型方法
end
class ClassA
include ModuleA
# 可以直接通过模型的命名空间调用模型方法
ModuleA . method2 # => "do method2.."
a = ClassA . new
a . method1 # => "do method1.."
end
NameSpace
在引入多个模块时如果常量出现同名问题可以指定具体的命名空间以使用正确的常量。
否则 ruby 将使用后引入的常量,且多次引入不会重新引入。
且在模块中的非模块方法(可以直接调用的,模块的自方法)同名时,引入模块的类实例没法精准指定具体方法,所以一定要注意。
复制 module C
Name = 'lisi'
def hello ( name )
p " #{ Name } : hello," << name
end
# module_function :hello
end
module B
Name = 'zhangsan'
def self.hello ( name )
p " #{ Name } : hello," << name
end
# module_function :hello
end
class A
include C
include B
p Name # => "zhangsan"
B . hello "wangwu" # => "zhangsan : hello,wangwu"
# C.hello "wangwu" ## => 报NoMethodError (undefined method `hello' for C:Module)
a = A . new
a . hello "wangwu" # => "lisi : hello,wangwu" 因为在moduleB 中hello为自方法
end
include & extend
复制 module Log
def class_type
"This class is of type: #{self . class} "
end
end
复制 class TestClass
include Log
end
tc = TestClass . new . class_type
puts tc #This class is of type: TestClass
复制 class TestClass
extend Log
# ...
end
tc = TestClass . class_type
puts tc # This class is of type: TestClass
在ruby
中使用include
也可以增加实例方法,因为include
有一个self.included
的钩子函数,可以用它来修改类中对于模块的引入。
复制 module Foo
def self.included ( base )
base . extend ( ClassMethods )
end
def foo
puts 'instance method'
end
module ClassMethods
def bar
puts 'class method'
end
end
end
class Baz
include Foo
end
Baz . bar # class method
Baz . new . foo # instance method
Baz . foo # NoMethodError: undefined method ‘foo’ for Baz:Class
Baz . new . bar # NoMethodError: undefined method ‘bar’ for #<Baz:0x1e3d4>
extend
也有一个叫self.extended
的方法,作用和include
中的self.included
类似。
同时included
方法可以用作文件夹在时的一些初始化操作:
复制 module A
def A.included ( mod )
puts " #{self} included in #{mod} "
end
end
module Enumerable
include A
end
# => prints "A included in Enumerable"
尤其值得注意的是,self
其实就是调用的该类的一个单例类,可以从下面的例子看到:
复制 car = "car"
class << car
def f1 ; puts "f1" ; end
def self.f2 ; puts "f2" ; end
class << self
def f3 ; puts "f3" ; end
def self.f4 ; puts "f4" ; end
end
end
car . f1 # => f1
car . singleton_class . f2 # => f2
car . singleton_class . f3 # => f3
car . singleton_class . singleton_class . f4 # => f4
参考资料:《Ruby中include和extend的比较》
module引入
不同于类,module
本身不能实例对象,所以module
通过extend
引入别的module
时,其中打方法会被添加到自身的metaclass
中,并且可以通过module
名直接调用:
复制 module ExM
def hello_exm
puts 'hello exm'
end
end
module InM
def hello_inm
puts 'hello inm'
end
end
复制 module Base
include InM ;
extend ExM ;
end
Base . hello_exm
# => hello exm
Base . hello_inm
# => NoMethodError (undefined method `hello_inm' for Base:Module)
而Base
通过include
引入到方法则会在Base
被其他class
通过include
引入时添加到class
到实例方法中:
复制 class KLASS_IN
include Base ;
end
KLASS_IN . hello_inm
# => NoMethodError (undefined method `hello_inm' for KLASS_IN:Class)
KLASS_IN . new . hello_inm
# => hello inm
此时在KLASS_IN
的self
方法和实例的方法中都不能找到hello_exm
方法:
复制 KLASS_IN . hello_exm
# => NoMethodError (undefined method `hello_exm' for KLASS_IN:Class)
KLASS_IN . new . hello_exm
# => NoMethodError (undefined method `hello_exm' for #<KLASS_IN:0x00007fbebd098610>)
可以先看一个例子:
复制 module SelfM
def self.hello_self
puts "hello myself"
end
end
module Base
include SelfM ;
extend SelfM ;
end
Base . hello_self
# => NoMethodError (undefined method `hello_self' for Base:Module)
可以看到在Base
中没有找到SelfM
中的方法。self
的方法其实存放在metaclass
中。而ruby中的引入不会将metaclass
中的方法引入。
所以在上面的例子中ExM
中的方法已经被添加到了Base
的metaclass
中,所以ExM
中的方法在KLASS_IN
的metaclass
和实例中都找不到对应方法。
如果Base
被extend
到其他到类,同样ExM
中的方法不会被引入到该类中,而InM
中到方法会被添加到该类的metaclass
中:
复制 class KLASS_EX
extend Base ;
end
KLASS_EX . hello_inm
# => hello inm
KLASS_EX . hello_exm
# => NoMethodError (undefined method `hello_exm' for KLASS_EX:Class)
还有一点值得注意的是,Module
类中提供了一个名为module_function(*args)
的方法。其可以将指定的方法添加到当前的module
中,并且同时在该module
被其他类引入时,将方法添加到类的实例中。
复制 module_function :mehtod_name
在缺省情况下,module_function
调用之后会作用于其后添加的所有方法,类似private
。
Rails引入
concern
Rails
中的ActiveSupport::Concern
通过其中的append_features(base)
方法:
复制 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 . include (dep) }
super
# look here!
base . extend const_get(:ClassMethods) if const_defined?(:ClassMethods)
base . class_eval( & @_included_block) if instance_variable_defined?(:@_included_block)
end
end
使得引入了ActiveSupport::Concern
的module
可以在其被其他类include
引入时,将其中名为ClassMethods
的module
中的方法添加到对应类的metaclass
中。
复制 module InM
def hello_inm
puts 'hello inm'
end
end
module BaseModel
extend ActiveSupport :: Concern
include InM
def hello_base
puts 'hello base module'
end
module ClassMethods
def hello_record
puts 'hello record'
end
end
end
class Re < ApplicationRecord
include BaseModel
end
Re . hello_record
# => hello record
而其中的其他方法,或者在该module
中通过include
引入的其他module
中的方法可以被引入到对应类的实例中。
复制 Re . new . hello_base
# => hello base module
Re . new . hello_inm
# => hello inm
autoload_path
通过命令行调用rails runner 'puts ActiveSupport::Dependencies.autoload_paths'
可以看到Rails
配置的autoload_paths
:
复制 $ bin/rails runner 'puts ActiveSupport::Dependencies.autoload_paths'
...
/Users/boohee/works/ruby/polestar/app/controllers
/Users/boohee/works/ruby/polestar/app/controllers/concerns
/Users/boohee/works/ruby/polestar/app/jobs
/Users/boohee/works/ruby/polestar/app/mailers
/Users/boohee/works/ruby/polestar/app/models
/Users/boohee/works/ruby/polestar/app/models/concerns
/Users/boohee/works/ruby/polestar/app/services
...
这些路径下的module
将会被Rails
自动加载,这使得我们在使用对应的module
时不用添加表示文件结构的命名空间
例如不使用autoload_path
的情况下,在app/modules
下添加共用的module
:
复制 .
└── commons
└── soft_delete.rb
soft_delete.rb
文件中module
,必须添加命名空间Commons
复制 module Commons :: SoftDelete
extend ActiveSupport :: Concern
def delete
update(deleted_at: Time . now)
end
def delete!
update!(deleted_at: Time . now)
end
end
在其他Record
引入时也需要添加命名空间Commons
:
复制 class User < ApplicationRecord
include Common :: SoftDelete
end
我们可以在项目根目录的application.rb
中指定Rails
自动将app/modules/commons
加载,这使得我们可以省去在引入时使用命名空间:
复制 config . autoload_paths += Dir [ " #{config . root} /app/models/[a-z]*s/" ] +
Dir [ Rails . root . join( "app/workers" )]
将app/modules/commons/soft_delete.rb
文件的module
修改为module SoftDelete
,Record
的引入则可以修改为:
复制 class User < ApplicationRecord
include SoftDelete
end
注: autoload_paths
在初始化过程中计算并缓存。目录结构发生变化时,要重启服务器。spring可能会缓存autoload_paths
,即使是重启了服务,修改目录后需要暂时关闭spring