先看代码:
module M
def a
puts 'm-a'
end
end
class A
def self. inherited(cls)
# cls.send(:include, M)
# 这里直接inculde 好像是不行的
# 等A initialize 时再self.send(:extend, M)倒是可以。。
end
end
class B < A
def a
def 'b-a'
end
end
我想为每一个继承自 A 的类做一些修改,使用 inherited 会被 B 的方法覆盖的,我想要的是这些修改覆盖掉 B 类的方法,以后再打开 B 类的修改我就不管了
其实就是 Rails 里,我有了现有的代码,然后有另一个项目,有一点点区别,以后也许会有很多不同的,我不想写 if 什么的,也不想在 before_filter 里面每次都 include 一个模块(以前有个项目就是,布一个项目,客户端请求哪个项目我就 extend 哪个项目的模块进来。。),只想等每个 Controller 类关闭之后,也就是 end 后根据我的 config 来用对应的模块中的方法覆盖此类中原来的方法。。不知道怎么做了
在@wppurking 及 @kenshin54 的帮助下目前的一个实现 见 22 楼
求优美的实现方式。。
另附一个有些烂的实现
require 'pry'
require 'rails'
NameSpace = "M"
module M
module B
def a
puts 'ma'
end
def b
puts 'mb'
end
end
end
def get_module(cls)
(NameSpace + "::" + cls.to_s).safe_constantize
end
class A
def self.inherited(cls)
super
_module = get_module(cls)
return unless _module
cls.class_eval do
def initialize
super
print self.class, ' init', "\n"
_module = get_module(self.class)
extend _module if _module
end
end
nil
end
end
class B < A
def a
puts 'ba'
end
end
class C < B
end
B.new.a
B.new.b
C.new.a
# Result
# B init
# ma
# B init
# mb
# C init
# ba
实现的就是如上效果,如果我 M 模块中有 B 模块的话,等同于 B include 了这个模块,而且模块中的方法会替换掉 B 类中定义的相同的方法,上面的弄影响了正常的 initialize 方法,虽然在 Rails 的 Controller 中一般都没有使用这个东东,相比使用 method_added 然后 remove_method,感觉应该好一点吧。。
通过 method_added 也可以使得 initialize 可以正常使用
话说也不需要啊,你之前的 include 的方式也可以啊,通过 config 配置来判断你要 include 什么。 载入什么不是重点吧。
有点不懂你的需求,呵呵
@badboy 关键是我在什么时候 include。。。 上那我代码里那个,a 方法被 B 的覆盖了,我想要的是模块 M 覆盖类 B 的 a 方法
你要在父类里,修改子类的方法? 子类调用 a 方法,a 会覆盖父类的 a,这是正常的继承。
你要让再父类去修改子类的 a。。。没想过!!! 为什么你不再 B 里去判断你的 include 呢?
有很多个 B,B1,B2 的,不同的还得 include 对应的模块,前提是有那个模块在,我总不能一个个去写吧。。
我现在正在给 A 写个 initialize,然后里面 self.send(:extend, M)
试验试验。。。
你这种情况要么在相关类都加载完后去执行 include,可以试试在 config.after_initialize 中执行,但是这对于一些 rails 启动时未加载,后续加载的类或者动态加载的类就无效了。
禁止子类或者 module 覆盖方法好像没这功能,但是有绕个圈来实现的,通过 method_added 来做,这个文章里有写,gem 比较老了,不知道能不能用,实现方式很简单,不能用可以自己再写个。
http://scie.nti.st/2008/9/17/making-methods-immutable-in-ruby/ https://github.com/up_the_irons/immutable
这需求类似 java 的 final method
@xjz19901211 不知道我的理解是否正确。
require 'active_support/core_ext'
module M
# 可以不需要 Concern 转而使用 self.included(mod) 加 mod.send(..) 处理
extend ActiveSupport::Concern
included do
alias_method :ori_a, :a
remove_method :a
end
def a
puts 'm-a'
end
end
class A
end
class B < A
def a
puts 'b-a'
end
# 因为需要 B 先有 a 方法, 才能够在 Module 中处理 a 方法.
# 如果是动态 include 那应该没有问题.
include M
end
b_klass = B.new
b_klass.a # m-a
b_klass.ori_a # b-a
@wppurking 这样是可以实现,不过每个模块要查找自己有定义的方法,然后 included 的时候再替换,最后就是那个 include M 要每个继承自 A 的类都要加上
我想要每个继承自 A 的类自动找到对应的模块然后加上。比如:B => M, B1 => M1....
就是只修改 A 这个类来实现
感觉是不是我想法有问题。。。。。。
以前我做过的事,rails 中的 before_filter 来弄
def load_project_module(project)
project_module_name = project.classify
return unless project_module_name.safe_constantize
controller_name = (params[:controller] + "_controller").classify
project_controller_name = project_module_name + "::" + controller_name
project_controller = project_controller_name.safe_constantize
return unless project_controller && project_controller_name == project_controller.to_s
self.send(:extend, project_controller)
end
这个在 Controller 里面确实去调用 extend 里的方法了,这个我真不知道是为什么。。 因为我刚刚试了下一个对象 extend 一个模块是不能覆盖掉已经有的方法。。 不知道什么情况,看来回公司后又要把 Ruby 元编程拿来看看了。。
@xjz19901211 换个方式,直接从 A 类处理了子类 include 的 module 的方法。感觉有点暴力啊,如果真要这么做一定要让所有单元测试跑通才行啊。
class A
class << self
# class macro 用于可动态调整, 例如
# B.class_eval do
# include M2
# check_super :m2_b
# end
# B.new.m2_b # m2-b
#
def check_super(method_name)
# 根据 method_name 查询所有的 module 的第一个 instance_method, 如果存在, 并且 self 不是 A 这个父类的话, 就使用 module 中的 method_name
first_module_method = self.included_modules.map { |_module| _module.instance_method(method_name) if _module.instance_methods(false).include?(method_name) }.first
self.send(:remove_method, method_name) if self != A and first_module_method
end
def method_added(method_name)
check_super(method_name)
end
end
end
module M
def a
puts 'm-a'
end
end
class B < A
include M
def a
puts 'b-a'
end
end
B.new.a # m-a
@wppurking 这个。。。确实的暴力,不过我一直都是用暴力解决的,我现在用 A 的 initialize 来实现,这样每次 new 都会 extend,至于 extend 为什么可以用,这个我真不知道,我在 Rails 里可以,直接开 irb 试不可以。。。
现在遇到的问题是:
class A
def initialize
# self.send(:extend, module)
end
end
class B < A
end
class C < B
end
# =========
module AM
module B
end
module C
end
end
然后我 C.new 时,只能管不到 B 了
看了你的实现我又学到新方法了,B 里的 include M 应该可以直接在 A 里做了,通过 inherited 回调来为 A 的所有继承者来 include 对应的模块,如果模块中有这个方法就移除类里的这个方法
接着试验去。。
@wppurking 这样移除方法,我还得写个判断,只有名字是 Xx::Yyy 下的模块有这个方法我才弄掉这个方法,这样就不怕别的模块来了我也把方法删了。。
绕了一圈,感觉应该差不多了,不过最初的那个如果知道一个类定义完了,这个问题还不知道,不知道有没有回调。。。
@wppurking 这样弄起来发现问题确实会很后,怕以后出了问题,不知道哪里找去。。 现在我的实现
module ProjectExt
def inherited(cls)
project_name = get_project_name
ext_project_module(project_name, cls)
cls.class_eval do
def self.method_added(method_name)
remove_method(method_name) if _included_modules_has_method?(method_name)
end
define_singleton_method "_included_modules_has_method?" do |method_name|
self.included_modules.each do |m|
next unless m.to_s =~ /^#{project_name}\:\:/
return true if m.instance_methods(false).include?(method_name)
end
return false
end
end
super
end
def ext_project_module(project_name, cls)
return unless project_name && project_name.safe_constantize
project_controller_name = project_name + "::" + cls.to_s
project_controller = project_controller_name.safe_constantize
return unless project_controller && project_controller_name == project_controller.to_s
cls.send(:include, project_controller)
end
def set_project_name(name)
@project_name = name.classify
end
def get_project_name
@project_name ||= (APP_CONFIG["project"] || "").classify
end
end
然后,测试
# encoding: utf-8
require 'spec_helper'
module M
module B
def a; 'ext-a'; end
end
end
class A
extend ProjectExt
set_project_name("m")
end
class B < A
def a; 'a'; end
def b; 'b'; end
end
class C
def a; 'a'; end
end
module M2
def a; 'other-a'; end
def b; 'other-b'; end
def c; 'other-c'; end
end
describe ProjectExt do
it '自动加载扩展模块,使用扩展模块的方法代替掉原有方法' do
B.new.a.should == "ext-a"
end
it '其它模块引入情况' do
class ::B
# 因为有M::B,所以这里定义不了
def a; 'a'; end
include M2
end
# 因为M2在M::B后面,所以这里会先找到M2中的函数
B.new.a.should == 'other-a'
B.new.b.should == 'b'
B.new.c.should == 'other-c'
end
it '引入非目标扩展模块不受影响' do
C.send :include, M2
C.new.a.should == 'a'
C.new.b.should == 'other-b'
C.new.c.should == 'other-c'
end
end
虽然 问题多,不过暂时在我项目上还不会有影响,先用着
主要就是后面再也不能定义已经被移除的方法了,然后,再来一个 include 相同的方法会覆盖了,不过这样我项目里暂时没什么问题。。 弄好其它的测试还是得多写写了
你看看这样写满足你要求吗?
#定义config
CONFIG={"A"=>["M1"],"B"=>["M2"],"C"=>["M3"]}
#module块
module M1
def a
puts "this is M1 on A"
end
end
module M2
def a
puts "this is M2 on B"
end
end
module M3
def a
puts "this is M3 on C"
end
end
#include负责向类(的实例)追加功能,而extend则只向某特定的对象追加模块的功能.
#这里修改父类
class Object
def self.load_config_module
CONFIG[self.to_s].each do |module_name|
eval("extend #{module_name}")
end if CONFIG && CONFIG[self.to_s]
end
end
#类
class A
load_config_module
end
class B < A
load_config_module
end
class C < A
load_config_module
end
puts "======A========"
A.a
puts "======B========"
B.a
puts "======C========"
C.a
结果:
$ ruby testrb.rb
======A========
this is M1 on A
======B========
this is M2 on B
======C========
this is M3 on C
就是每一个类都需要加一句 load_config_module 如果不加,就不会加载,加载什么就根据你的 config 的配置
@badboy 谢谢你的解答,不过这样还是不能覆盖掉类下面原的方法,如:
class Object
def self.load_config_module
CONFIG[self.to_s].each do |module_name|
eval("include #{module_name}")
end if CONFIG && CONFIG[self.to_s]
end
end
#类
class A
def a
puts 'a-a'
end
load_config_module
end
class B < A
load_config_module
def a
puts 'b-a'
end
end
A.new.a
B.new.a
A 还是输出 a-a,B 输出了 b-b
感觉是我表述的不清楚。。
你刚才的代码里
class B < A
load_config_module
def a
puts 'b-a'
end
end
改成
class B < A
def a
puts 'b-a'
end
load_config_module
end
那么 module 的会覆盖类里的 a,这样应该也是可以满足你的,不过感觉挺不舒服
@badboy 这样每个类里在结束都要加 load_config_module 这一句,如我标题所示,我想知道怎么知道这个类结束了,然后自动做这个操作。。。。
#定义config
CONFIG={"A"=>["M1"],"B"=>["M2"]}
#module块
module M1
def a
puts "this is M1 on A"
end
end
module M2
def a
puts "this is M2 on B"
end
end
module M3
def a
puts "this is M3 on C"
end
end
#include负责向类(的实例)追加功能,而extend则只向某特定的对象追加模块的功能.
#这里修改父类
class Object
def self.load_config_module
CONFIG[self.to_s].each do |module_name|
eval("extend #{module_name}")
end if CONFIG && CONFIG[self.to_s]
end
end
#类
class A
def self.inherited
load_config_module
end
end
class B < A
end
class C < A
end
puts "======A========"
A.a
puts "======B========"
B.a
puts "======C========"
C.a
#结果
$ ruby testrb.rb
======A========
this is M1 on A
======B========
this is M2 on B
======C========
this is M3 on C
@badboy 你真的是转圈圈了。。 请把 extend 改成 include,虽然这个关系不大,最重要的是在 B 和 C 中定义一个方法,然后让模块中的方法来覆盖他
动态加进去呢?嘿嘿,淫荡就淫荡得彻底一点
#定义config
CONFIG={"A"=>["M1"],"B"=>["M2"],"C"=>["M3"]}
#module块
module M1
def a
puts "this is M1 on A"
end
end
module M2
def a
puts "this is M2 on B"
end
end
module M3
def a
puts "this is M3 on C"
end
end
#include负责向类(的实例)追加功能,而extend则只向某特定的对象追加模块的功能.
#这里修改父类
class Object
def load_config_module
CONFIG[self.class.to_s].each do |module_name|
eval("extend #{module_name}")
end if CONFIG && CONFIG[self.class.to_s]
end
end
#类
class A
def initialize
load_config_module
end
end
class B < A
def a
puts "fuck b"
end
end
class C < A
def a
puts "fuck c"
end
end
puts "======A========"
A.new.a
puts "======B========"
B.new.a
puts "======C========"
C.new.a
#结果
$ ruby testrb.rb
======A========
this is M1 on A
======B========
this is M2 on B
======C========
this is M3 on C
不过如果你的业务很复杂,建议你做成 gem 包 或者参考@kenshin54 说的,在 rails 层面上在 config.after_initialize,加载完后再载入你的混入。。。
@badboy 然后这样就和我问题里写的一个实现差不了多少了(请看本贴最后的更新内容),initialize 挂了 而且你这里如果是 C < B 的话,B 的模块不会自动加上来的,嘿嘿
@badboy 我直接写个文件里了,这里东西懒的写 Gem 了,这个不折腾了,我感觉痛苦了。。
应该是我这种做法有问题,就这样算了,以后想到更好的方法再说。。
不太明白楼主的需求,ruby OO 语法也不太熟悉。简单表达下我的理解:
class A
def a
p 'called from A'
self._a
end
end
class B < A
def _a
p 'called from B'
end
end
B.new.a # called from A, called from B
# change some codes in parent class A
class A
def a
p 'called from A'
p 'add some new modules'
self._a
end
end
B.new.a # called from A, add some new modules, called from B