Ruby中如何判断祖先链是否重写initialize以条件调用super
解决方案
你遇到的问题本质是Ruby中BasicObject的#initialize方法不接受任何参数,当mixin祖先链中没有其他自定义initialize方法时,super调用就会落到该方法触发报错。以下是两种符合要求的实现方案:
方案1:基类兜底(可控场景下最优)
如果所有mixin都是用于你自己维护的业务类,只需要在这些类的公共基类中定义一个接受任意关键字参数的空initialize方法作为兜底即可,该方法会在所有mixin的initialize调用完后被触发,不会走到BasicObject的逻辑。
# 所有业务类的公共基类 class BusinessBase # 兜底初始化方法,接受任意关键字参数,无额外逻辑 def initialize(**) end end # 正常定义你的mixin模块即可,不需要额外判断 module SomeModule def initialize(arg1: , arg2: , **rest) @arg1 = arg1 @arg2 = arg2 # 直接调用super即可,最后会走到基类的兜底方法 super end end # 业务类使用示例 class MyBusinessClass < BusinessBase # 按任意顺序引入mixin都不会报错 include SomeModule, OtherModule end
该方案优势是mixin代码简洁无额外逻辑,不用关心引入顺序,所有参数都会按原生super逻辑完整传递,不会出现参数丢失问题。
方案2:mixin内判断super方法所有者(适合对外发布的mixin场景)
如果你的mixin需要提供给第三方不可控的类使用,无法要求对方继承指定基类,可以在每个mixin的initialize方法末尾判断super方法的所有者,仅当不属于BasicObject时才调用。
module SomeModule def initialize(arg1: , arg2: , **rest) @arg1 = arg1 @arg2 = arg2 # 获取当前initialize方法对应的下一级super方法 super_method = method(__method__).super_method # 仅当super方法不属于BasicObject时才调用,参数完整传递 super unless super_method.owner == BasicObject end end
验证示例
module M1 def initialize(a:, **rest) @a = a puts "M1 初始化完成" super_method = method(__method__).super_method super unless super_method.owner == BasicObject end end module M2 def initialize(b:, **rest) @b = b puts "M2 初始化完成" super_method = method(__method__).super_method super unless super_method.owner == BasicObject end end # 无需继承自定义基类也能正常使用 class TestClass # 任意顺序引入模块都正常 include M1, M2 end # 实例化不会报错,参数完整传递 obj = TestClass.new(a: 1, b: 2) p obj.instance_variable_get(:@a) # => 1 p obj.instance_variable_get(:@b) # => 2
该方案优势是不依赖上层类的实现,mixin可以独立使用,同样支持任意引入顺序,super会传递所有当前方法收到的参数,不会出现参数丢失问题。
内容的提问来源于stack exchange,提问作者sandre89
相关产品推荐
相关产品推荐

