设为首页收藏本站|繁體中文

Project1

 找回密码
 注册会员
搜索
查看: 2573|回复: 4
打印 上一主题 下一主题

[交流讨论] 最近重学了下ruby,写了个替代 alias 的东西自娱自乐

[复制链接]

Lv2.观梦者

梦石
0
星屑
777
在线时间
70 小时
注册时间
2017-12-2
帖子
14
跳转到指定楼层
1
发表于 2021-2-17 11:22:04 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式

加入我们,或者,欢迎回来。

您需要 登录 才可以下载或查看,没有帐号?注册会员

x
之前修改脚本时,有些情况下是在自带脚本的某些方法上做修改,还需要调用原方法的,为了和其它脚本兼容,一般用alias+重定义,有时会嫌alias写得麻烦,想写一个自动alias一个时间戳别名,于是学了一下法术(就是那本《Ruby元编程》),最早想通过写模板代码走eval,后来随着工作忙碌,写一半忘了。

这几天春节在家有空了,整理了下之前写的东西,重新看了眼书,重写了下代码,就不用eval了。

  1. #encoding: utf-8
  2. # -------------------------------------------------------------------
  3. # - KRuby module by KurozawaRuby
  4. # -------------------------------------------------------------------
  5. module KRuby
  6.   VERSION = '1.0.0215'
  7. end

  8. # -------------------------------------------------------------------
  9. # - KRuby::ClassExt by KurozawaRuby
  10. # -------------------------------------------------------------------
  11. module KRuby::ClassExt
  12.   private
  13.   # -----------------------------------------------------------------
  14.   # - 在某方法调用后执行 block。
  15.   # -   block 参数即原方法参数
  16.   # -   不会修改方法的返回值
  17.   # - 使用: after:method_name { |*args| do_sth }
  18.   # -----------------------------------------------------------------
  19.   def after(method_name, &block)
  20.     return unless block_given?
  21.     old = instance_method(method_name)
  22.     define_method(method_name) do |*args, &b|
  23.       result = old.bind(self).call(*args, &b)
  24.       instance_exec(*args, &block)
  25.       result
  26.     end
  27.   end

  28.   # -----------------------------------------------------------------
  29.   # - 在某方法调用后执行 block。
  30.   # -   block 第一个参数为原方法返回值,后续参数为原方法参数
  31.   # -   该方法的返回值会被修改为block的返回值
  32.   # - 使用: after!:method_name { |result, *args| do_sth }
  33.   # -----------------------------------------------------------------
  34.   def after!(method_name, &block)
  35.     return unless block_given?
  36.     old = instance_method(method_name)
  37.     define_method(method_name) do |*args, &b|
  38.       result = old.bind(self).call(*args, &b)
  39.       instance_exec(result, *args, &block)
  40.     end
  41.   end

  42.   # -----------------------------------------------------------------
  43.   # - KRuby::ClassExt::WrapMethod
  44.   # -   作为 around! 块的参数使用,封装原始方法及调用参数
  45.   # -----------------------------------------------------------------
  46.   class WrapMethod
  47.     attr_reader :org_method, :args, :block
  48.     def initialize(old, args, block, obj)
  49.       @org_method = old
  50.       @args = args
  51.       @block = block
  52.       @obj = obj
  53.     end
  54.     # ---------------------------------------------------------------
  55.     # - 调用原方法
  56.     # ---------------------------------------------------------------
  57.     def call
  58.       @org_method.bind(@obj).call(*@args, &@block)
  59.     end
  60.   end

  61.   # -----------------------------------------------------------------
  62.   # - 使用 block 替换某方法,但可以在 block 内调用原方法。
  63.   # -   block 的参数为 WrapMethod 对象,由原方法、参数等构成
  64.   # -   在 block 内可通过 WrapMethod#call 调用原方法
  65.   # - 使用: around!:method_name { |org| org.call && do_sth }
  66.   # -----------------------------------------------------------------
  67.   def around!(method_name, &block)
  68.     return unless block_given?
  69.     old = instance_method(method_name)
  70.     define_method(method_name) do |*args, &b|
  71.       instance_exec(
  72.         WrapMethod.new(old, args, b, self),
  73.         &block
  74.       )
  75.     end
  76.   end
  77. end

  78. Object.send(:extend, KRuby::ClassExt)
复制代码


这堆代码提供几个类宏,分别是 after, after!, around! 代码不长,

首先是 after 和 after! 这两个,是表示在某个方法调用后执行,用法很简单
  1. class << DataManager
  2.   after:load_database do
  3.     puts "load complete"
  4.   end
  5. end

  6. class Game_Battler
  7.   after:refresh do
  8.     puts "actor #{name} with hp_rate = #{hp_rate}"
  9.   end
  10. end
复制代码

这两段分别是在 DataManager.load_database 这个静态方法调用后和 Game_Battler#refresh 这个实例方法调用后在控制台输出一些东西,可以看到这里的 self 也是处理好了的。

两个after的区别是不带感叹号的不会改变方法返回值,然后块参数也有一点区别,带感叹号的第一个参数是原方法返回值。

如果要在方法调用前执行某段代码,照着两个 after 改一下也可以整出类似 before 和 before! 这样的东西。也可以使用下面说的 around! 实现。

然后是 around! 这个有点向 java aop 里面的 @around 切面,你需要手动执行原方法,比起 after 之流灵活很多,其实之前的 after 也可以使用 around! 实现(下面一段代码可替换上面代码里 after 的实现)
  1. def after(n, &b)
  2.   return unless b
  3.   around!(n) do |org|
  4.     res = org.call
  5.     instance_exec(*org.args, &b)
  6.     res
  7.   end
  8. end
复制代码


类宏 around 的块参数只有一个,是 KRuby::ClassExt::WrapMethod 类的实例,可以使用 WrapMethod#call 调用原方法并获取返回值,也可以获取原方法的方法对象(org_method)和入参(args, block)

性能损失:
测下性能,使用以下跑分代码(after2即为之前的 around! 实现的 after )
  1. module Test end
  2. class << Test
  3.   def a
  4.     test
  5.   end
  6.   
  7.   def b
  8.     test
  9.   end
  10.   
  11.   def c
  12.     test
  13.   end
  14.   
  15.   def d
  16.     test
  17.   end
  18.   
  19.   def test
  20.     "a" * 100
  21.   end

  22.   after:a do 1 end
  23.   after2:b do 1 end

  24.   alias c_org c
  25.   def c
  26.     c_org
  27.     1
  28.   end
  29. end



  30. Benchmark.bm do |x|
  31.   x.report('after') { 1000000.times { Test.a } }
  32.   x.report('after2') { 1000000.times { Test.b } }
  33.   x.report('alias') { 1000000.times { Test.c } }
  34.   x.report('org') { 1000000.times { Test.d } }
  35. end
复制代码


我机器的结果 # i7-9750H ruby 1.9.2p290 (2011-07-09) [i386-mingw32]
  1.       user     system      total        real
  2. after  0.906000   0.031000   0.937000 (  0.942526)
  3. after2  1.438000   0.000000   1.438000 (  1.437284)
  4. alias  0.469000   0.000000   0.469000 (  0.458915)
  5. org  0.437000   0.016000   0.453000 (  0.463683)
复制代码


可以看到百万次调用,使用 alias 速度和原始的差不多,而使用 after 要慢很多(0.5秒)而使用 around! 实现的 after2 又比 after 还慢0.5秒

不过一百万次调用0.5秒-1秒的性能损失我觉得也可以接受吧,rm 里就算是每帧都调10次也不到1毫秒的影响,更何况我们实际使用基本不可能到那种程度。

上述代码(除了跑分)在我的VA是能跑的,XP和VX没试过。

java.lang.NullPointerException

Lv4.逐梦者

梦石
2
星屑
6682
在线时间
501 小时
注册时间
2018-3-23
帖子
533

R考场第七期银奖

2
发表于 2021-2-17 13:02:02 | 只看该作者
我记得taroxd的核心引擎有这些东西(

点评

刚看了一下,他的代码比我的要细很多,还考虑到了访问级别等很多东西,但是没有关系,我写着玩的(  发表于 2021-2-17 14:07
祝好。
回复 支持 反对

使用道具 举报

Lv4.逐梦者

梦石
0
星屑
11352
在线时间
611 小时
注册时间
2016-8-25
帖子
1400

R考场第七期纪念奖

3
发表于 2021-2-17 13:57:42 | 只看该作者
alias是什么东西啊???????

点评

瓜瓜 惨  发表于 2021-2-17 17:18
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 注册会员

本版积分规则

拿上你的纸笔,建造一个属于你的梦想世界,加入吧。
 注册会员
找回密码

站长信箱:[email protected]|手机版|小黑屋|无图版|Project1游戏制作

GMT+8, 2024-11-16 09:09

Powered by Discuz! X3.1

© 2001-2013 Comsenz Inc.

快速回复 返回顶部 返回列表