Ruby 嵌套模块作为命名空间

Posted

技术标签:

【中文标题】Ruby 嵌套模块作为命名空间【英文标题】:Ruby nested modules as namespaces 【发布时间】:2013-09-06 07:04:45 【问题描述】:

我有一个嵌套的模块结构,仅用于命名空间;没有混入类等。所以我有这样的代码:

module Lib

  module A
    def A.foo
      puts 'Lib::A::foo'
    end
  end

  module B
    def B.bar
      puts 'Lib::B::bar'
    end
  end

end

现在假设我想在模块A 中再添加一个辅助方法,并且我希望两个模块都能轻松使用它。我得到以下信息:

module Lib

  module A
    def A.foo
      puts 'Lib::A::foo'
    end
    def A.helper
      puts 'Lib::A::helper'
    end
  end

  module B
    def B.bar
      puts 'Lib::B::bar'
      A::helper
    end
  end

end

它似乎有效,但它有一些我想摆脱的缺点: 我不想一直从B 内部调用helper 的全名(A::helper)。我更愿意以某种方式告诉 Ruby 这个命名空间前缀是“默认的”,并简单地将其称为helper。在 C++ 中,我可以在命名空间 B 内写 using A::helper,它会解决问题。但是如何在 Ruby 中做到这一点呢?

我尝试在B 中添加include Aextend A,但它们都不起作用。它们似乎只在类中工作,当这些模块混合在一起时,但当它们是独立的,仅用于命名空间时。

还有其他方法可以让它按我想要的方式工作吗?

哦,还有一件事: 假设一个不同的场景,我希望 A::helper 只能在 A 的方法内部使用,因为它只是一些实现函数,我在其中分解出 A 内部许多函数使用的一些通用代码,但现在我不希望它对外界可见,只对A 的方法可见。我该怎么做?

我尝试使用module_function + 从所有其他应该隐藏的函数中删除A. 前缀,但是对于A 中的其他方法,它们也被隐藏了,因为它们是实例方法,并且模块不能被实例化。那么如何才能将一个模块方法对外界隐藏起来,并且仍然允许其他模块方法在内部使用它呢?

编辑 为什么要投反对票?我试图尽可能清楚,我需要的是默认一个命名空间在另一个命名空间内完全摆脱长的完全限定名称(不仅仅是别名它们更短),而且我的问题根本不涉及类和对象,只是用于命名空间目的的普通模块。我还能怎么解释呢?

命名空间机制在 Rubynative 中似乎不像在 C++ 等语言中那样完全支持,这不是我的错,而且它似乎只是一个副作用使用模块和类来具有真正命名空间的一些功能(它们像鸭子一样嘎嘎叫,它们有鸭嘴,但它们是鸭嘴兽,而不是鸭子,并且不应将它们宣传为它们显然不是的命名空间,因为它只会使从其他语言进入 Ruby 的人与真正的命名空间混淆),也不是你显然不理解其他编程语言的概念,如果它们不容易可能的话在 Ruby 中(因为我看到您似乎假装我的问题不存在并恢复到您在 Ruby 中可以轻松做的事情;但这不是我需要的)。

为什么与我的问题相关的唯一答案已被删除?不酷;。

【问题讨论】:

在不同的帖子中提出这些问题会更好......而不是在一个帖子中......我认为 【参考方案1】:

好的,由于与我的问题相关的唯一答案已被删除,我将尝试自己回答我的问题,基于@sawa 已删除的答案(感谢@sawa 提示我正确的方向) .我对其进行了一些修改,以更好地满足我的需求并更优雅。稍后我将描述为什么最初的 @sawa 的答案不是我想要的。好的,不用多说,这是我自己的解决方案尝试:

module Lib

  module A
    extend self
    def foo
      puts 'Lib::A::foo'
    end
    def helper
      puts 'Lib::A::helper'
      foo
    end
  end

  module B
    extend A
    def self.bar
      puts 'Lib::B::bar'
      helper
    end
  end

end

puts 'Calling Lib::A::foo:'
Lib::A::foo        # => Lib::A::foo

puts 'Calling Lib::A::helper:'
Lib::A::helper     # => Lib::A::helper; Lib::A::foo

puts 'Calling Lib::B::bar:'
Lib::B::bar        # => Lib::B::bar; Lib::A::helper; Lib::A::foo

它的工作原理如下: 首先,它将所有方法定义为特定模块类本身的实例方法(在本例中为A)。但是,为了让它们在不实例化的情况下可供外部使用(这对于模块来说毕竟是不可能的),我将extend A 模块与自身一起使用,这使得这些方法也成为它的类方法。但是由于它们也是模块A 的实例方法,因此可以从该模块的其他方法内部调用它们,而无需在它们前面加上模块名称。模块B 也是如此,extends 本身也与模块A 的方法一起使用,使它们成为他自己的。然后我也可以在 B 的方法中调用它们而无需前缀,就像我想要的那样。

如您所见,我不仅可以从外部调用两个模块的方法,就好像它们是它们的类的方法一样,我可以从B::foo 调用A::helper 而无需完全限定其名称,而且我还可以调用A::foo 来自 A::helper 没有资格。这正是我所需要的,而且似乎按我的预期工作。

这种方法的唯一问题可能是它们的接口混合在一起。这在 B 内部并不是什么大问题,因为这正是我真正想要的:能够像访问 B 的方法一样访问 A 的方法,而无需在它们前面加上一个完整的限定条件.所以我得到了我应得的。但这可能会从外部引起问题,因为它是B 的实现细节,它在内部使用A 的方法。它不应该泄漏到外部世界,但确实如此。我会尝试通过访问控制以某种方式修复它,也许它会以某种方式成为可能。

编辑:是的,可以通过在B 中的extend A 之后插入以下行来完成:

private_class_method *A.public_instance_methods

这样B 可以在内部调用A 的方法,但不能从外部访问它们。

现在@sawa 的原始解决方案出了什么问题:

它一直在使用 third 模块,只是通过它来代理接口。对我来说,这是一个丑陋的 hack,而不是一个优雅的解决方案,因为它引入了这个额外的模块,这会使此类库的用户感到困惑。他们将不知道应该使用A 还是C,以及为什么要使用这样的装置。仅仅通过观察它并不清楚它是如何工作的。它需要一些更彻底的分析来弄清楚它的真正作用以及为什么要这样构造。这不是一个干净的设计。

另一方面,在我的解决方案中,最初设计的只有两个模块,并且对于这个库的用户来说,它们的目的应该很清楚。有这个奇怪的extend self,但它似乎仍然是 Ruby 中的一个更常见的习惯用法,而不是到处传播代理模块。

感谢大家的尝试。下次尽量不要那么自大(当你看到有人问问题时,他并不总是菜鸟)并专注于你心爱的一种真正的语言(不要误会我的意思,我喜欢 Ruby 语言,它很酷,而且干净,但与任何语言一样,它也有一些缺点,最好寻求解决它们,而不是埋头假装完全没有问题,因为这不是该语言的设计目标)。

【讨论】:

【参考方案2】:
module E
  module A
    def foo
      puts 'E::A::foo'
    end
    def helper
        puts 'E::A::helper'
    end    
  end

  module B
    extend A
    def B.bar
      puts 'E::B::bar'
      helper
    end
  end
end

E::B.bar #=> E::B::bar & E::A::helper

【讨论】:

是的,这将起作用,只要您不需要相互调用A 的方法。例如,helper 将无法调用foo,反之亦然,因为它们现在是实例方法。但我需要两者:A 内部的交叉调用和B 内部的A 默认命名空间,以完全摆脱长命名空间前缀。 (我知道我可以将它们简称为别名,但这仍然与通过将前缀设为默认值来完全摆脱前缀不同。)【参考方案3】:

我建议重新考虑您的基本流程。这句话是一个危险信号:

我有一个嵌套的模块结构,仅用于命名空间;不得混入仅用于命名空间的类等;没有混入类...

这是使用模块的错误方式,即使您现在不同意,您可能会通过更多经验发现这一点。我建议更多地研究 Ruby 继承,以及“在野外”使用模块的方式。网上有大量优秀的博客、指南和资料,只需 google 一下...

【讨论】:

如果 Ruby 本身不能支持其他语言支持的常见 OOP 设计策略,我建议重新考虑 Ruby,而不是埋头苦干,责备我没有做 Ruby 中可能的事情并遵循 Ruby 方式( TM值)。或者将它们显然不是的模块称为“命名空间”。请不要说我是菜鸟,因为我在其他语言的 OO 设计方面有很多经验,而且我也不是 Ruby 菜鸟。 Ruby 的设计与其他语言不同,它被设计为灵活的,其基本理念是“有不止一种方法可以做到这一点”。没有人建议您是“菜鸟”,但您显然不知道 Ruby 的一些既定原则。一个模块的目的是混入,这就是为什么我提到有“更多”可以看的原因,这是一个复杂的话题。看,不到一分钟,我就找到了一个完全适用于您的用例的来源:blog.rubybestpractices.com/posts/gregory/… “模块的目的是混入其中,这就是为什么我提到有“更多”可以查看的原因”我非常了解 mixins,并且我什至在我原来的帖子开头提到它不是我需要的。我只需要命名空间,并且模块被宣传为适合该工作的工具。那么为什么他们不是呢?它们似乎只是 假装 是命名空间,但它们并不比类更多的命名空间,如果一个人不能别名或默认它们(这是引入命名空间的最初目的) )。 感谢文章的链接。它根本没有解决我的问题,因为它只是一些基本的命名空间以避免名称冲突,仅此而已。但无论如何,谢谢,因为我发现了更多的例子,说明 Ruby 如何在许多不同的地方把它全部搞砸(比如默默地混合来自不同命名空间的两个类的接口而不给出任何错误消息)。

以上是关于Ruby 嵌套模块作为命名空间的主要内容,如果未能解决你的问题,请参考以下文章

如何在 Ruby 类/模块命名空间中翻译模型?

在 Ruby 中动态定义命名类

Python之命名空间闭包装饰器

函数嵌套函数对象命名空间

映射到命名空间模块时将 prop 作为模块名称传递

如何使用 swig C++ 命名空间作为 python 模块公开