Ruby中如何通过Module共享无继承类初始化逻辑并保留关键字参数
方案1:无eval安全版(仅共享初始化逻辑)
如果你能接受在每个类的initialize里显式声明age:和name:关键字参数,这个方案最稳妥,完全规避eval的安全风险和调试问题:
module AgeAndNameInitializationConcern def self.included(base) base.attr_reader :name, :age end private def initialize_age_and_name(age:, name:) @age = age.to_i @name = name.to_sym end end class Person include AgeAndNameInitializationConcern def initialize(age:, name:, height:) initialize_age_and_name(age: age, name: name) @height = height end end class Dog include AgeAndNameInitializationConcern def initialize(age:, name:, breed:) initialize_age_and_name(age: age, name: name) @breed = breed end end
这个方案的优势是逻辑直观、无黑魔法、参数校验完全原生,写错参数会直接抛出清晰的错误提示。
方案2:完全共享参数定义+初始化逻辑
如果要连age: name:的参数定义都省掉,不需要在每个类里重复写,用prepend + 参数透传就能实现,完全不需要用到binding和eval:
module AgeAndNameInitializationConcern def self.included(base) base.attr_reader :name, :age # 将初始化逻辑前置到类的继承链 base.prepend Initializer end module Initializer def initialize(age:, name:, **extra_params) # 执行共享初始化逻辑 @age = age.to_i @name = name.to_sym # 透传专属参数给类自身的初始化方法 super(**extra_params) end end end class Person include AgeAndNameInitializationConcern # 仅需要声明类专属的关键字参数即可 def initialize(height:, **) @height = height end end class Dog include AgeAndNameInitializationConcern def initialize(breed:, **) @breed = breed end end
验证效果:
# 参数校验完全生效 Person.new(age: "18", name: "张三", height: 180) # => 正常初始化,@age为18,@name为:张三 Dog.new(age: "2", name: "旺财", breed: "柴犬") # => 正常初始化 Person.new(name: "张三", height: 180) # => 抛出ArgumentError: missing keyword: :age,和原生参数定义的报错完全一致
这个方案完全满足需求:
- 共享参数定义:
age:name:只需要在模块里写一次 - 共享初始化逻辑:赋值逻辑不需要重复编写
- 保留关键字参数:不需要改成哈希参数,原生参数校验完全生效
- 无继承要求:两个类不需要有共同父类
注意:你原来用
binding+eval的方案不推荐生产环境使用,eval执行动态字符串会有代码注入风险,且报错堆栈不清晰,调试难度很高。
内容的提问来源于stack exchange,提问作者sandre89
相关产品推荐
相关产品推荐

