代码之家  ›  专栏  ›  技术社区  ›  Sean

Ruby类名解析中的模糊性

  •  3
  • Sean  · 技术社区  · 7 年前

    我最近在Rails代码库中进行了一些代码重构,在代码库中我转换了一些定义了如下类的代码(省略了类体):

    foo/user_bar.rb :

    module Foo
      class UserBar; end
    end
    

    foo/sub_user_bar.rb :

    module Foo
      class SubUserBar < UserBar; end
    end
    

    …to:

    foo/bar/user.rb :

    module Foo
      module Bar
        class User; end
      end
    end
    

    foo/bar/sub_user.rb :

    module Foo
      module Bar
        class SubUser < User; end
      end
    end
    

    这引入了一个微妙的问题,即已经存在一个名为 User (轨道模型),以及 SubUser 是从那个衍生出来的,不是从 用户 在同一个模块中。我想这是因为Rails加载类的顺序是不可预测的…?

    总之,我发现我可以通过声明 子用户 类如下:

    class SubUser < self::User
    

    …这似乎有效,尽管我在少量的搜索中找不到它的任何例子。不过,看起来确实有点奇怪。现在我担心我真的应该为模块中的每一个类都这样做,以防引入同名的顶级类。

    在我公司的代码库中,有两种类型的模块内类声明:

    class A::B::Foo; end
    

    还有:

    module A; module B; class Foo; end; end; end
    

    我一直比较喜欢后一种方法,因为它允许访问模块中的其他名称,而不需要完全限定的名称,但是现在我发现当我使用它时,可能存在一个陷阱。那么这里的最佳实践是什么呢?

    • 不要在 module ;始终使用完全限定的名称( class A::B::C::Foo < A::B::C::Bar )
    • 在中声明类 模块 但始终完全限定超类名称( module A; module B; module C; class Foo < A::B::C::Bar )
    • 在中声明类 模块 使用非限定名,但始终使用 self:: 在同一模块中扩展类时
    • 在中声明类 模块 使用非限定名称,但要注意顶级命名空间中可能发生的名称冲突(似乎有风险)

    或者Ruby和/或Rails是否提供了一些其他更清洁的方法来解决这个问题?

    2 回复  |  直到 7 年前
        1
  •  3
  •   tadman    7 年前

    通常,如果存在歧义,则指定完整的命名空间路径:

    module Foo
      module Bar
        class SubUser < Foo::Bar::User; end
      end
    end
    

    这可能看起来很冗长,但也很具体明确。

    要引用的文本顶级 User 你可以指定 ::User .

        2
  •  1
  •   Cary Swoveland    7 年前

    模块由常量标识。 因此,模块查找的分辨率由查找常量引用的过程决定。根据David Flanagan和Yukihero Matsumoto(2008年第1版)(第261-262页)的“Ruby编程语言”,按以下顺序执行常量查找,其中 mod 是包含对常量引用的最内部模块:

    • 国防部 .
    • 下一个封闭模块,继续,直到不再有封闭模块为止。(本步骤不考虑顶级/全局常量)。类方法 Module::nesting 返回按搜索顺序搜索的模块数组。
    • 元素 mod.ancestors ,按索引顺序(即继承层次结构)。
    • 顶级常量定义。
    • 方法返回的值 Module#const_missing 国防部 如果已经定义了方法,则表示为接收器的常量和表示为参数的符号的常量。

    让我们看看我们可以通过插入 puts Module.nesting.inspect puts ancestors 语句转换为代码。

    module Foo
      class UserBar
        puts "nesting for UserBar=#{Module.nesting.inspect}"
        puts "#{self}.ancestors=#{ancestors}"
      end
    end
      # nesting for UserBar=[Foo::UserBar, Foo]
      # Foo::UserBar.ancestors=[Foo::UserBar, Object, Kernel,
      #   BasicObject]
    
    module Foo
      class SubUserBar < UserBar
        puts "nesting for SubUserBar=#{Module.nesting.inspect}"
        puts "#{self}.ancestors=#{ancestors}"
      end
    end
      # nesting for SubUserBar=[Foo::SubUserBar, Foo]
      # Foo::SubUserBar.ancestors=[Foo::SubUserBar,
      #   Foo::UserBar, Object, Kernel, BasicObject]
    

    module Foo
      module Bar
        class User
          puts "nesting for User=#{Module.nesting.inspect}"
          puts "#{self}.ancestors=#{ancestors}"
        end
      end
    end
      # nesting for User=[Foo::Bar::User, Foo::Bar, Foo]
      # Foo::Bar::User.ancestors=[Foo::Bar::User, Object, Kernel,
      #   BasicObject]
    
    module Foo
      module Bar
        class SubUser < User
          puts "nesting for SubBar=#{Module.nesting.inspect}"
          puts "#{self}.ancestors=#{ancestors}"
        end
      end
    end
      # nesting for SubBar=[Foo::Bar::SubUser, Foo::Bar, Foo]
      # Foo::Bar::SubUser.ancestors=[Foo::Bar::SubUser,
      #   Foo::Bar::User, Object, Kernel, BasicObject]
    

    这告诉我们如果有一个班 User 不包含在 Foo (如在Rails中)该类将成为引用 用户 在班里 Foo::UserBar Foo::SubUserBar ,但不在 Foo::Bar::User Foo::Bar::SubUser . 这种理解应该有助于组织模块以避免名称空间问题。

    1由于类是模块,每当我引用下面的“模块”时,我指的是类以及不是类的模块。