Ruby 元编程中的环绕别名 (around Alias) 使用场景是什么?

xiaoronglv · 2015年06月10日 · 最后由 cicholgricenchos 回复于 2015年06月18日 · 3274 次阅读

You can write an Around Alias in three simple steps:

  1. You define method a
  2. You alias a method to b.
  3. You redefine a call the old method from the new method.
class String
  alias :orig_length :length

  def length
    "Length of string '#{self}' is: #{orig_length}"
  end  
end

"abc".length
#=> "Length of string 'abc' is: 3"

这段代码来自 Ruby Quick tips

我画了一个丑丑的线框图。

我的问题

  1. 环绕别名的使用场景是什么?
  2. 直接写一个新的方法,在里面调用老方法不就得了,干吗把事情搞得这么麻烦?

我能想到的使用场景

  1. 该方法是 Public,已经被调用者频繁调用。如果直接启用新方法,需要调用者修改自己的代码。
  2. 通过环绕别名,既修改了自己的逻辑,又不需要调用者更改自己的代码。

这个想法正确吗?

这就是 alias method chain pattern…

#1 楼 @hooooopo

炮哥,能说的清楚点吗,不太懂。

:)

目的是为了把你的逻辑塞到不归你管的代码中 (例如 rails 的方法) 去

版本升级提示 deprecated message 是这么弄出来的吧 ❓

#2 楼 @xiaoronglv alias_method_chain 就是 DHH 对你说的 Around Alias 的一个封装,之前广泛应用在 Rails 内部。 曾引起群众的不满,群众表示这可以用 super、prepend、subclass 等更 OO 的方式替代。

现在的应用场景就是 3 楼所说的。在不修改原有接口的情况下,改变不归你管的接口的行为。当然,如果你想引入新接口的话就可以不用这么麻烦。比如:

User.find -> User.find_with_foo

匿名 #6 2015年06月10日

#5 楼 @hooopo :plus1:

#5 楼 @hooopo 维基样的存在 :godmode:

@hooooopo 你是我在现实中见到的素质最好的 Rubyist

在没有 Module.prepend 之前,如果你要使用 monkey pacth 去修改或扩充现有方法的逻辑的话,只能采用 alias,否则就必须借助派生子类来干这个事情……

我在阅读 thor 的源代码时发现这个 gem 大量使用了 alias, 不过作者自己说只是为了 backward compatible . 这算一个应用场景吧。

至于用新方法调用老方法那肯定是不一样的,alias 的方法相当于把旧方法复制了一份。

class Foo
  def bar
    puts "bar"
  end

  alias biz bar
end

class Foo
  def bar
    puts "not bar"
  end
end

puts Foo.new.bar
puts Foo.new.biz

面对这种覆盖的情况,如果采用新调旧的方式是无法实现上面的效果的。

回到 thor 的例子,根据那个 pr 的说明,作者觉得使用 command 这个术语要比 task 要好。 那么使用 alias 之后,就相当于这些方法都有了一份自己的拷贝,那么要添加 deprecated message 就很容易了 @etnl 。 下面是例子

class Foo
  def command_method
    puts "run method"
  end

  alias task_method command_method

  def self.call_before_all_methods
    all_instance_methods = instance_methods - Class.instance_methods
    all_instance_methods.each do |x|
      # 名字包含 task 的方法会输出一个 deprecated message
      # 如果我想法变了,只要把"task"改成"command"就行了
      if x.to_s.include? "task"
        class_eval <<-END
          alias old_#{x} #{x}
          def #{x}
            puts "method: #{x} is deprecated"
            old_#{x}
          end
        END
      end
    end
  end

  private_class_method :call_before_all_methods
  call_before_all_methods
end

foo = Foo.new
foo.command_method
foo.task_method

注意那段 aop 的元编程也使用了 alias 方法,但是和我要说明的 alias 的特性没任何关系,仅仅把它当做一种 aop 的实现来理解就行了。

12 楼 已删除
匿名 #13 2015年06月10日

#12 楼 @hooooopo 没什么地方可去,只好在 rubychina 灌水 ... ...

不喜欢 alias,如果要重新打包一个方法,我会把方法作为 UnboundMethod 存起来,再重新绑定调用,这起码不会污染作用域中的命名

需要 登录 后方可回复, 如果你还没有账号请 注册新账号