最近,我在Rails代码库中进行了一些代码重构,在该代码库中,我转换了一些代码,这些代码定义了这样的类(消除了类体):
foo/user_bar.rb
:
module Foo
class UserBar; end
end
foo/sub_user_bar.rb
:
module Foo
class SubUserBar < UserBar; end
end
...至:
在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
(Rails模型)的顶级类,并且SubUser
是从中派生的,而不是从User
在同一模块中。我猜这是因为Rails加载类的顺序是不可预测的...?
无论如何,我发现可以通过声明SubUser
类来消除歧义:
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
中声明类,但始终完全限定超类名称(module A; module B; module C; class Foo < A::B::C::Bar
)module
中声明类并使用非限定名称,但在同一模块中扩展类时始终使用self::
module
中声明类并使用不合格的名称,但要注意顶级名称空间中可能发生的名称冲突(似乎有风险)还是Ruby和/或Rails提供了其他更干净的方法来解决此问题?
答案 0 :(得分:3)
通常,在您不确定完全指定名称空间路径的地方:
module Foo
module Bar
class SubUser < Foo::Bar::User; end
end
end
这可能看起来很冗长,但又是明确而明确的。
如果要引用文字顶级User
,则应指定::User
。
答案 1 :(得分:1)
模块由常量标识。 1 因此,模块查找的分辨率由查找常量引用的过程确定。根据David Flanagan和Yukihero Matsumoto(第一版)的“ Ruby编程语言”。 2008(p。261-262),将按以下顺序执行常量查找,其中mod
是最内部的模块,其中包含对常量的引用:
mod
。mod.ancestors
的元素,按索引顺序(即继承层次结构)。 mod
是接收者,表示为符号的常量是自变量(前提是已定义该方法)。让我们看看通过在代码中插入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中一样),则该类将成为类{中对User
的引用对象{1}}和Foo::UserBar
,但不在Foo::SubUserBar
或Foo::Bar::User
中。这种理解应该有助于组织模块以避免命名空间问题。
1因为类是模块,所以每当我在下面提到“模块”时,我都在指代类以及不是类的模块。