如何在父母 class 中打开 class 而不影响 ruby 中的其他 sub-classed 实例

how do you open a class in parental class without affecting to other sub-classed instances in ruby

假设你有这个 Base class 你需要扩展。

class Base
  class Child
    def im
      "from base child"
    end
  end

  def initialize
    @child = Child.new
  end

  def test
    @child.im
  end
end

对于常规情况,您只需要扩展 Base class 并为其 class 添加方法。

class A < Base
  # add methods 
end

但是,如果 sub-class 想像这样改变父母内部 class 的行为,这是行不通的。我认为这是因为 Base#initialize 调用了 Child.new。 (不是 B 调用)

class B < Base
  class Child
    def im
      "from b child"
    end
  end
end

puts B.new.test # "from base child"

如果我更改为 class Base::Child,其他 sub-class 个实例将受到影响。

class B < Base
  class Base::Child
    def im
      "from b child"
    end
  end
end

puts A.new.test # "from b child"
puts B.new.test # "from b child"

如果我想让A和B表现得像下面这样,我应该如何打开class B中的内部class? (或者我应该在 Base 中进行更改吗?)

class B < Base
  class ????? Child
    def im
      "from b child"
    end
  end
end

puts A.new.test # "from base child"
puts B.new.test # "from b child"

我想我明白了。 Base 应该调用 self.class:: 到 Child.new 调用

class Base
  class Child
    def im
      "from base child"
    end
  end

  def initialize
    @child = self.class::Child.new
  end

  def test
    @child.im
  end
end

class A < Base
end

class B < Base
  class Child
    def im
      "from b child"
    end
  end
end

puts A.new.test
puts B.new.test

你想做的事情在 Ruby 中非常不寻常,因为嵌套的 classes 不是继承的。

当您执行以下操作时

class B < Base
  class Child
  end
end

有两种可能性:要么从 Base 中得到 Child,要么 Child 未找到并被视为新常量。在这种情况下,Ruby 实际上将 Child 视为一个新常量(我猜测是为了防止意外猴子修补父 class 中的 Child)所以你不是完全打开 Base::Child,您正在定义一个完全独立的 class!试试吧:

B::Child == Base::Child
# false!

如果它们相等,这就是您通过指定 Base::Child 得到的结果,您会遇到您在问题中指出的问题:修改 subclass 中的 Child 会将其修改为所有子classes。但同样,这是意料之中的,因为 嵌套 class 不是继承的 。要么只有一个 Child 所有子class 共享,要么每个 class 都有自己的 Child,与 Base::Child.[=25= 完全无关]

然而,通过使用元编程和 inherited 钩子,可以使嵌套的 classes 继承。就像我说的,这是非常规的,所以它可能会让阅读您的代码的其他 Rubyists 感到奇怪。但如果你真的需要这样做,方法如下:

class Base
  class Child
    def im
      "from base child"
    end

    def common
      "shared by all subclasses"
    end
  end

  def self.inherited subclass
    # define a new Child class that inherits from Base::Child
    subclass.const_set "Child", Class.new(self::Child)
  end

  def initialize
    @child = self.class::Child.new
  end

  def test
    puts @child.im
    puts @child.common
  end
end

class A < Base
   # now this is opening Child, but not Base::Child! Rather A::Child < Base::Child
   class Child
     def im
       "from a child"
     end
   end
end

class B < Base
  # same here, opening a subclass of Base::Child so we can make changes but still share behavior with Base::Child
  class Child
    def im
      "from b child"
    end
  end
end

A.new.test
# from a child
# shared by all subclass
B.new.test
# from b child
# shared by all subclasses