【问题标题】:What are namespaces for ? what about usages?命名空间有什么用?用法呢?
【发布时间】:2011-04-06 16:09:26
【问题描述】:
  1. 命名空间的用途是什么?

  2. 而且,更重要的是,它们是否应该用作 java 中的对象(具有数据和函数并试图实现封装的事物)?这个想法牵强吗? :)

  3. 还是应该将它们用作 java 中的包?

  4. 还是应该将它们更普遍地用作模块系统或其他东西?

【问题讨论】:

    标签: namespaces clojure


    【解决方案1】:

    鉴于您使用 Clojure 标记,我想您会对特定于 Clojure 的答案感兴趣:

    命名空间的用途是什么?

    Clojure 命名空间、Java 包、Haskell / Python / 任何模块......在非常高的层次上,它们都是相同基本机制的不同名称,其主要目的是防止重要代码库中的名称冲突。当然,每个解决方案都有自己的小曲折和怪癖,这些曲折和怪癖在给定语言的上下文中是有意义的,并且在它之外没有意义。这个答案的其余部分将处理 Clojure 特有的曲折和怪癖。

    Clojure 命名空间组 Vars,它们是包含函数(最常见)、宏函数(编译器用来生成适当形式的宏展开的函数,通常用 defmacro 定义的容器;实际上它们只是常规的 Clojure 函数,尽管它们向编译器注册的方式有些神奇),偶尔还有各种“全局参数”(例如,clojure.core/*in* 用于标准输入)、Atoms / Refs 等。引入的协议工具在 Clojure 1.2 中,协议由 Vars 支持,就像单个协议函数一样;这是协议为表达问题提供解决方案的方式的关键(但这可能超出了这个答案的范围!)。

    按理说,命名空间应该对某种相关的 Var 进行分组。一般来说,创建命名空间是一种快速且廉价的操作,因此在开发的早期阶段使用单个命名空间是非常好的(并且确实很常见),然后随着独立功能块的出现,将它们分解到它们自己的命名空间中,冲洗并重复...只有公共 API 的一部分需要预先在命名空间之间分配(或者更确切地说:在稳定版本之前),因为这样某某的函数驻留在命名空间中,所以- and-so 当然是 API 的一部分。

    而且,更重要的是,它们是否应该用作 java 中的对象(具有数据和功能并试图实现封装的东西)?这个想法牵强吗? :)

    通常,答案是否定的。如果您将它们视为具有许多静态方法,没有实例方法,没有公共构造函数并且通常没有状态的类,您可能会得到一个与事实相差不远的图片(尽管偶尔可能会有一些“类数据成员”形式为Vars 持有 Atoms / Refs);但可以说,最好不要尝试将 Java 式的隐喻应用于 Clojure 习语,并将命名空间视为一组函数等,而不是“一个包含一组函数的类”或类似的东西。

    这个一般规则有一个重要的例外:命名空间在其ns 形式中包含:gen-class。这些是为了实现一个稍后可能被实例化的 Java 类,它可能具有实例方法和每个实例的状态等。请注意,:gen-class 是一个互操作特性——纯 Clojure 代码通常应该避免它。

    还是应该将它们用作 java 中的包?

    它们服务于一些与包设计服务相同的目的(如上所述);这个类比虽然确实存在,但并没有那么有用,只是因为包组合在一起的东西(Java 类)根本不像 Clojure 命名空间组合在一起的东西(Clojure Vars),各种“访问级别” (private / package / public 在 Java 中,{:private true} 或不在 Clojure 中)的工作方式非常不同等等。

    话虽如此,我们必须记住,命名空间和位于特定包中的包/类之间存在一定的对应关系。名为foo.bar 的命名空间在编译时会在包foo 中生成一个名为bar 的类;这尤其意味着命名空间名称应至少包含一个点,因为所谓的单段名称显然会导致类被放入“默认包”中,从而导致各种怪异。 (例如,我发现 VisualVM 的分析器不可能注意到单段命名空间中定义的任何函数。)

    另外,deftype / defrecord 创建的类型不驻留在命名空间中。定义命名空间foo.bar 的文件中的(defrecord Foo [...] ...) 表单会在包foo.bar 中创建一个名为Foo 的类。要使用来自另一个命名空间的 Foo 类型,必须使用 :import 来自 foo.bar 包的类 Foo -- :use / :require 不起作用,因为它们从命名空间中拉入 Vars,哪些记录/类型不是。

    因此,在这种特殊情况下,名称空间和包之间存在一定的对应关系,希望利用某些较新语言功能的 Clojure 程序员需要注意这些对应关系。一些人发现,这为原本不属于互操作领域的特性赋予了“互操作的味道”(defrecord/deftype/defprotocol 是一种很好的抽象机制,即使我们忘记了它们在实现JVM 上的平台速度),并且在未来的 Clojure 版本中,这种风格可能会被取消,因此deftype & Co. 的名称空间名称/包名称对应关系可以被视为实现细节。

    还是应该将它们更普遍地用作模块系统或其他东西?

    它们一个模块系统,这确实是它们应该被使用的方式。

    【讨论】:

      【解决方案2】:

      Java 中的包有自己的命名空间,它提供了类的逻辑分组。它还有助于防止命名冲突。例如,在 java 中,您会发现 java.util.Date 和 java.sql.Date - 两个具有相同名称的不同类,它们的名称空间不同。如果您尝试将两者都导入到 java 文件中,您将看到它不会编译。至少有一个版本需要使用其显式命名空间。

      【讨论】:

        【解决方案3】:

        从语言独立的角度来看,命名空间是一种隔离事物的方式(即封装在一个 sens 中)。这是一个更一般的概念(例如,请参见 xml 命名空间)。您可以通过多种方式“创建”命名空间,具体取决于您使用的语言:包、静态类、模块等。所有这些都为它们包含的对象/数据/函数提供了命名空间。这允许更好地组织代码,隔离功能,倾向于更好的代码重用和适应性(作为封装) 正如“Python 之禅”中所述,“命名空间是一个非常棒的想法——让我们做更多这样的事情!”。

        【讨论】:

          【解决方案4】:

          将它们视为类的容器。就像您有一个用于构建字符串的帮助器类并且您希望在业务层中使用它一样,您将使用诸如 MyApp.Business.Helpers 之类的命名空间。这允许您的类包含在有意义的位置,因此当您或其他引用您的代码的人想要使用它们时,它们可以很容易地找到。再举一个例子,如果你想使用 SQL 连接助手类,你可能会使用类似的东西:

          MyApp.Data.SqlConnectionHelper sqlHelper = new MyApp.Data.SqlConnectionHelper();

          实际上,您会使用“using”语句,因此您不需要完全限定命名空间来声明变量。

          保罗

          【讨论】:

            猜你喜欢
            • 2012-10-24
            • 2010-09-12
            • 2018-02-06
            • 1970-01-01
            • 2011-12-10
            • 2011-02-21
            • 2020-04-26
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多