新手问题 How to override instance method in nested class in Ruby?

Mtt · 2020年03月12日 · 最后由 wakasa 回复于 2020年03月13日 · 2461 次阅读

有这样一个类

class A
    class B
        def b
            puts "A::B#b"
        end
    end

    def a
        B.new.b
    end
end

我想继承 A,并重写 class B 里面的方法 A::B#b"

class A_CHILD < A
    class B < A::B
        def b
            puts "A_CHILD#B.b"
            super()
        end
    end
end

看起来应该能 work, 但结果往往出乎意料

A.new.a #=> A::B#b
A_CHILD.new.a #=> A::B#b  这里没有调用A_CHILD自己的B的实例方法b

那么问题来了,可以在不打开 A::B 这个类的前提下,重写内部的方法呢?

A_CHILD 里面没有 a 方法,所以会回到父类里面去调用 a。 回到父类之后的 a 自然用的就是父类里面的 class B。 所以想使用 class B < A::B 中的 b,就需要在 class A_CHILD < A 中重写 a。

不成熟的想法,仅供参考

class A
  class B
    def b
        puts "A::B#b"
    end
  end

  def a
    B.new.b
  end
end

class A_CHILD < A
  def a
    b = B.new
    def b.b
      puts "A_CHILD#B.b"
      super
    end
    b.b
  end
end

没有必要把 B 放到 A 里面,你的问题是 A 以及子类依赖另外一个类,你需要把这个类传给 A 或者 A 的子类。把 B 放进 A 不解决问题。

require 'active_support/core_ext'

class B
  def b
      puts "A::B#b"
  end
end

class A
  class_attribute :b_like_class, default: B

  def a
    b_like_class.new.b
  end
end

class B2 < B
  def b
    puts "B2.b"
    super()
  end
end

class A_CHILD < A
  self.b_like_class = B2
end

A.new.a
A_CHILD.new.a

@ShowLew 谢谢!有一点不明白『回到父类之后的 a 自然用的就是父类里面的 class B』。我理解是哪个类调用的就在当前 context 去寻找 B, 而不是去找父类的 B.

@piecehealth 同样感谢!看完代码惊呆了,还可以这样搞,收货颇多。

注意到 class A 里面嵌套 class B 事实上只是在打开 class A::B。继承时 B 作为不变常量只是有了个新别名。换句话说,A_CHILD::B == A::B

关于你的需求, 如果只是A_CHILD的a方法需要特别的b方法,A类并不需要,那么写个A_CHILD#a就足够了。

class A_CHILD < A
    def a
      puts "A_CHILD#B.b" # 你需要的特别逻辑
      super # 如果还需要原来父类A#a的逻辑
    end
end
需要 登录 后方可回复, 如果你还没有账号请 注册新账号