Ruby-Module初探

Ruby-Module初探

module是什么?

Ruby里有一种叫module的东西,他和我们比较熟知的Class有很多类似之处,同样是组合一堆常量、变量和方法的单元,但是module不能像类一样被实例化。
Class被定义为Module的子类,但Class不只是module的一个子集,Class的某些独有的特性是module不具备的,比如继承或派生的概念。

扩展阅读:

那Class怎么成为Module子类的?

module的两大用途

1. 命名空间(Namespace)

我们可以使用module去构建某一模组的命名空间,这一模组可能包括一些方法或属性,甚至包括一些类,class其实也构建了命名空间,但是module比class显得更简洁、可控,我们可以打开module去为其添加新的属性、方法或类,但是这种更改是静态或是全局的。
比如,如果我们要写一套用于BASE64编解码的功能模块,因为BASE64是一套既定的规则(算法),我们没必要去定义一个类,然后每次要做BASE64编解码的时候,在内存空间中去一个新建一个对象来使用类中的方法;或者到这里我们想到了在类中定义静态方法来避免新建动态对象,的确,在诸如Java这样的语言中,我们习惯这样做,但是Ruby能为我们提供更纯粹的方式,那就是定义一个module:

module Base64
  def self.encode(plain_text)
    # do_encoding
  end

  def self.decode(base64_str)
    # do_decoding
  end
end

编解码是一个很泛的概念,比如还有URL编码、图片编码、视频编码等等,现在我们定义这个模块,就我们实现的编解码方法划定了一个特定命名空间;有了这个模块,我们就可以从以下方面来灵活应用:
一方面,在想做编解码的地方,可以用模块加半角圆点前缀的方式(如Base64.encode(pain_text))直接调用这个模块所提供的方法;
请注意到,我们在模块方法的定义中都加了self.前缀,如果我们想单独使用这个模块而不是作为其它模块或类定义的一部分,这是必要的,因为module的使用实际上和class相差无几,而module又不能实例化,我们需要将其定义为这个module对象(module也是对象哦!那self就是当前正在定义的这个module)的单例方法(静态方法);当然,如果这个模块里的所有方法都想要有被单独使用的能力,那我们可以通过extend的方式来简化代码的编写,就像下面这样:

module Base64
    extend self

    def encode(plain_text)
      # do_encoding
    end

    def decode(base64_str)
      # do_decoding
    end
end

另一方面,如果我们想为一个正在定义中的模块或类增加BASE64的处理能力,那我们就可以通过module的第二个强大的功能来完成这个需求,这个功能就是下面我们提到的混入。

2. 混入(Mixins)

前文提到module没有继承和派生的概念,我们不能用继承关键字去继承一个module,如果要在一个特定Class (ConcreteClass)中去使用另一个已定义的module (ConcreteModule),我们需要使用include的方式将ConcreteModule里定义的东西组装到我们当前所要设计的单元中来,然后ConcreteModule里定义的东西就好像我们直接在ConcreteClass里定义的一样。

# demo_include_module.rb
module ConcreteModule
  ConstA = 'constant A defined in ConcreteModule'

  def method_a
    puts 'method_a defined in module ConcreteModule'
  end
end

class ConcreteClass
  include ConcreteModule

  def method_b
    puts 'method_b defined in class ConcreteClass'
  end
end

puts ConcreteClass::ConstA
ConcreteClass.new.method_a
ConcreteClass.new.method_b

# demo_include_module.rb
module ConcreteModule
  ConstA = 'constant A defined in ConcreteModule'

  def method_a
    puts 'method_a defined in module ConcreteModule'
  end
end

class ConcreteClass
  include ConcreteModule

  def method_b
    puts 'method_b defined in class ConcreteClass'
  end
end

puts ConcreteClass::ConstA
ConcreteClass.method_a
ConcreteClass.new.method_a

执行这段Ruby代码:

> ruby demo_include_module.rb

将会得到如下输出:

constant A defined in ConcreteModule
method_a defined in module ConcreteModule
method_b defined in class ConcreteClass

如果我们想让ConcreteModule中的方法变成ConcreteClass的类方法,只需将类名后面的include换为extend来引入ConcreteModule即可。

# demo_extend_module.rb
module ConcreteModule
  ConstA = 'constant A defined in ConcreteModule'

  def method_a
    puts 'method_a defined in module ConcreteModule'
  end
end

class ConcreteClass
  extend ConcreteModule

  def method_b
    puts 'method_b defined in class ConcreteClass'
  end
end

puts ConcreteModule::ConstA
ConcreteClass.method_a
ConcreteClass.new.method_a

然后执行代码:

> ruby demo_extend_module.rb
constant A defined in ConcreteModule
/xx/yy/demo_extend_module.rb:20:in `<top (required)>': undefined method `method_a' for #<ConcreteClass:0x2e663c8> (NoMethodError)
method_a defined in module ConcreteModule
    from -e:1:in `load'
    from -e:1:in `<main>'

Process finished with exit code 1

可以看到,extend引入的模块里的方法变成了目标类(对象)的单例方法,而且Ruby不允许我们像调用实例方法那样去在一个new出来的对象上调用单例方法。

扩展问题:

  1. 如果ConcreteModule本来就extend了self,那我们再通过include的方式引入ConcreteModule会有什么效果?
  2. ConcreteModule中定义的常量会以怎样的方式进入ConcreteClass?

上文提及的include和extend就是module的混入,通过混入,我们可以引入预先定义的各种模块,组成更强大的模块或类,并且能够方便地从模块中获得静态方法和实例方法。

钩子方法

我们在定义一个module时,还可以为其定义钩子方法,钩子方法名以动词的过去分词形式出现,当模块被以不同方式(include, extend)加入其它模块或类时,对应的*ed(过去分词)回调方法将得到调用,有了钩子方法,我们可以去监控定义了钩子方法的模块被其它定义所引用的情况:

# demo_module_included_hook.rb
module ConcreteModuleBase
  def self.included(other)
    puts "#{other} included #{self}"
  end
end

class ConcreteClass
  include ConcreteModuleBase
end

# cc = ConcreteClass.new

执行包含以上代码的.rb文件:

>ruby demo_module_included_hook.rb
ConcreteClass included ConcreteModuleBase

通过输出结果我们可以看到,程序侦测到了ConcreteModuleBase被引用的情况,这里已经体现了Ruby动态编程的些许魅力(关于Ruby动态编程,这里暂不讨论)!
除了included, extended, 我们还可以定义inherited, method_added, singleton_method_added等钩子方法来侦测不同行为的发生,真是很nice的一个特性。

小结

以上讨论了module的基本概念,两大用途,以及Ruby的钩子机制怎样去监控一个特定module是怎样被引用或是被扩展的,这些远不是module的全部内涵,比如模块函数(module_function)、作用域(scope),还有module的加载机制,均未涉及。
module的更多机制、特性或是用法技巧,有待更多的讨论和发掘。本次讨论,我们以最后一个扩展问题来结束:

扩展问题:

Ruby中没有专门定义接口(interface),为什么?

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值