【Ruby】Mixins扩展方式之include,extend和prepend
Mixin定义
首先,mixin是module模块,它用于在class之间共享提供可复用的的方法而不采用继承的方式。
通常在Ruby中用module来创建mixins,示例如下:
module Greetabledef greet"Hello!"end
end
不同于继承,可以将这个module通过mixin方式(include, extend, prepend)进任何的class。
3种方式使用Mixin
include—混入为实例方法
当使用include,对应module的方式会变成class的实例方法,示例如下:
module Greetabledef greet"Hello!"end
endclass Personinclude Greetable
endp = Person.new
puts p.greet # => "Hello!"
此时Ruby会把模块插入到类的继承链中,对应的位置是类的父类之前,可通过如下查看:
Person.ancestors
# => [Person, Greetable, Object, Kernel, BasicObject]
extend—混入为类方法(单例方法)
使用extend时,模块中的方法会变成类方法,示例如下:
module Identifiabledef identity"I’m a class method!"end
endclass Robotextend Identifiable
endputs Robot.identity
# => "I’m a class method!"
此时Ruby会把模块混入到对象的单例方法中,这样模块的方法就成为了这个对象的自身的方法。
prepend—类似include,顺序不同
prepend也会把模块混入为实例方法,但与include方法不同的是,模块会被插入到类之前,因此模块的方法可以覆盖类自身的方法,同时还能通过super调用原方法。示例如下:
module Loggingdef greetputs "Logging: greet was called"superend
endclass Userdef greet"Hello from User"endprepend Logging
endu = User.new
puts u.greet
输出结果如下:
Logging: greet was called
Hello from User
此时查看继承链如下所示:
User.ancestors
# => [Logging, User, Object, Kernel, BasicObject]
因此ruby会先调用Logging#greet,然后在其中通过super调用User#greet。
三者比较如下:
| 功能 | include | extend | prepend |
|---|---|---|---|
| 添加的方法类型 | 实例方法 | 类方法(单例方法) | 实例方法 |
| 方法查找顺序 | 类 → 模块 → 父类 | 单例类 → 模块 | 模块 → 类 → 父类 |
| 常见用途 | 添加实例行为 | 添加类级行为 | 覆盖或拦截原有方法 |
| 示例场景 | Enumerable、Comparable | Rails 的 ClassMethods 模块 | 日志、监控、拦截器等 |
实际应用场景
通过上述过程可以发现,Ruby查找方法时,不同混入方式会改变模块在继承链中的位置从而改变查找的顺序。
include应用
class VowelFinderinclude Enumerabledef each@string.scan(/[aeiou]/) { |v| yield v }end
end
Enumerable 一个模块(mixin),它提供map、reduce、select等功能,但要求类中必须定义一个each方法,一旦定义了each,Enumerable的所有方法就能自动工作。这就是 include 的经典用法 —— 给实例增加集合行为。
extend应用
Rails中的模块经常会在include的同时自动extend类,以添加类方法。示例如下:
module Trackabledef self.included(base)base.extend ClassMethodsendmodule ClassMethodsdef trackedputs "Tracking enabled for #{self.name}"endend
endclass Userinclude Trackable
endUser.tracked # => "Tracking enabled for User"
perpend应用
Rails、Devise、ActiveRecord等框架中经常使用prepend来在核心方法前后插入逻辑。示例如下:
module Auditdef saveputs "Audit: saving record..."superend
endclass Modeldef saveputs "Saving to DB"endprepend Audit
endModel.new.save
# => Audit: saving record...
# => Saving to DB
prepend允许在不修改原方法的情况下,对其进行包装、拦截、增强。
