原文地址:http://www.cnblogs.com/lidabo/archive/2012/12/15/2819865.html

Package vs. Namespace

我们知道,重用性(reusebility)是软件工程中一个非常重要的目标。重用,不仅仅指自己所写的软件(代码、组件等等)可以被重复利用;更广义的重用是指不同的人,不同的团队,不同的公司之间可以互相利用别人的成果。另外,对于大型软件,往往是由多个团队共同开发的,这些团队有可能分布于不同的城市、地区、甚至国家。由于这些原因,名字管理成为一个非常重要的因素。

由于C语言本身不提供名字管理的机制(C语言的static命名解决的是可见性问题,这些名字不会输出给外部,但我们要讨论的名字空间和这个问题并不完全一样),为了解决名字冲突的问题,大家一般会选取加前缀的方法;而前缀规则往往是: ${project name}_${name};更加安全的命名规则将前缀分了更多的级别:${project name}_${module name}_${name}。

这种方案在现实生活中有大量类似的例子。比如:中国的很多城市都有滨河路,如果你谈话的对象都明白你所指的城市,你只需要说滨河路,大家都会明白你的所指。但如果情况不是这样,你就需要加上前缀,说明这到底是乐山的滨河路,还是成都的滨河路。你在邮寄信件的时候,这一点体现的最为直接。

所以,如果存在一个全局的管理,C语言的这种方案应该是非常有效的。但它的缺陷是,这可能会造成很长的名字,而每次在引用一个名字的时候你都必须给出全名。

这是一件非常麻烦的事情。你不妨想象一下,明明大家都知道你所谈的是乐山的滨河路,但你不得不在每次谈到它的时候,都要说“中国四川省乐山市滨河路”,你会多么的痛苦。

为了解决这类问题,并给出一个行之有效的管理方案。随后的编程语言,无论是C++,Java还是C#都提供了自己的名字管理的机制。这些方案在本质上有其统一的思想,但操作方式存在着一定的差异。

在前面C语言的方案里,本身体现了分级管理的方式。分级管理是一种非常自然而有效的手段。比如,互联网的域名。它通过分级的命名保证了一个名字的全局唯一性,其排列方式是从小范围到大范围(这既是因为西方的书写习惯,也是为了方便。其实从这一点上,我们可以发现,如果一个人的预读习惯是从左到右的话,从小到大的排布方式则非常便于节省时间,比如 “滨河路,乐山市,四川省,中国”。在我们从左边的信息已经知道我们的所指时,则可以跳过或忽略后面的信息。而从大到小的排布方式则可以避免错误,因为我们首先了解了限定条件,最后读到滨河路的时候,我们已经确定我们的所指了) 。我们可以在前面加上名字,指定更小的范围。比如:wsd.wmsg.sps.motorola.com 说明这是motorola公司的SPS部门的wmsg部门的wsd组。

Java使用这种方式来命名包(Package),只不过把书写方式反过来。这种方式可以非常有效的保证命名的统一。比如,一个名为mlca的项目的mmi模块包可以命名为:com.motorola.sps.wmsg.wsd.mlca.mmi,而其engine模块包可以命名为:com.motorola.sps.wmsg.wsd.mlca.engine。

这样,当不同的团队,公司之间的代码放在一起进行使用时,在一个名字不冲突的情况下,我们只需要简单的使用它。当引起冲突的时候,我们指定其全名就行了。比如,上述的两个包中都有一个名为Message的class,如果我们的另外一个package中的某个class要同时使用这两个包,在引用Message类的时候,我们需要指明它来自于哪个包。如下:

import com.motorola.sps.wmsg.wsd.mlca.mmi;
import com.motorola.sps.wmsg.wsd.mlca.engine;

// 我们需要指明class Message来自于哪个包.
public class Foo extends com.motorola.sps.wmsg.mlca.mlca.mmi.Message {
...
}

而C++和C#则提供了namespace的概念来支持这种方式。你可以在全局的空间内指定自己的namespace,然后还可以在某个namespace内制定更小范围的namespace。虽然C++和C#本身没有推荐任何namespace的命名方式(其实反域名的方式也是Java推荐的,并非强制),但我们也可以使用上述方式。比如下面的C# code:

namespace com.motorola.sps.wmsg.wsd.mlca.mmi
{
// MMI Stuff
...
}

namespace com.motorola.sps.wmsg.wsd.mlca.engine
{
// Engine Stuff
...
}

当我们同时使用这两个模块时,如果出现名字冲突,也许要通过指定namespace来指明。比如:

class Foo: com.motorola.sps.wmsg.wsd.mlca.mmi.Message
{
...
}

Java的package本身没有子包的概念,所有package都是并列的关系,没有谁包含谁的问题。比如:org.dominoo.action和org.dominoo.action.asl之间绝对没有包与子包的关系。它们是各自独立的包,各自拥有自己的class/interface的集合。在org.dominoo.action.asl的某个java文件里,如果想引用org.dominoo.action里的某个class/interface,则必须import org.dominoo.action。

C++/C#的namespace方案则不然,一个namespace可以有自己的sub-namespace,我们不妨将namespace也称为package,那么C++/C#的package之间就可能存在包与子包的关系. 比如:

namespace org.dominoo
{
   namespace action
   {
     namespace asl
     {
       ....
     }
   }

namespace constraint
   {
     namespace ocl
     {
       ....
     }
   }
}

在这个例子中,action和constraint都是org.dominoo的子包,而它们又各自拥有自己的子包asl和ocl。

所以,对于一个全局的名字空间,C语言无法直接进行名字空间分离,而Java则可以从全局的名字空间里分离出独立的名字空间,但C++/C#则可以进一步将各个名字空间进行进一步分离。如下图:

|------------------|
| global namespace |
|                   |
|                  |
|                  |
|------------------|
         C 语言

|-------------------|
| global namespace |
|                   |
| |---| |---| |---| |
| | A | | B | | C | |
| |---| |---| |---| |
|-------------------|
       Java 语言

|----------------------------|
| global namespace            |
|                            |
| |-------------| |--------| |
| | A            | | B       | |
| | |---| |---| | | |---| | |
| | | C | | D | | | | E | | |
| | |---| |---| | | |---| | |
| |-------------| |--------| |
|----------------------------|
       C++/C# 语言

所以,Java的Package方案只对全局的名字空间进行了一次划分,本质上只是为语言提供了一个命名前缀方案,只是通过命名前缀的分级管理来保证名字的唯一性。它唯一的作用就是为了避免名字冲突。

而C++/C#则提供了对任何一个空间进行再次划分的能力。在Java中org.dominoo和org.dominoo.asl之间是完全没有包含关系的各自独立的包,但在C++/C#中,dominoo.asl则和明显是dominoo的一个子包。

事实上,如果仅仅为了避免命名冲突,像C++/C#这样复杂的方案并无必要,Java的方案就足够了。但C++/C#这种方案可以带来其它的便利:

1、软件开发的本质就是自上而下依次分解的,每一层都有自己的定义,并且这种定义可以作为下一层所有子系统的公共服务,多层次的树状结构符合这种逻辑。C++/C#方案用最自然的方式满足了这种划分关系。事实上,这种方案和文件管理的思路是一样的。

2、一个程序一旦using哪个namespace,就可以通过它向下访问它的子包,而无需指出全路经。比如,在上面的图中,如果一个程序写了using namespace A,则它在访问C包中的class Foo时,只需要写C::Foo,而不需要写全路径::A::C::Foo。在Java中,由于A,C是并列的关系,为了访问C中的内容,必须明确指出import C。然后在访问Foo而产生名字冲突的情况下,必须指出全路径。

3、当程序身处某个包的时候,在不产生名字冲突的情况下,可以直接访问外部包中的定义。由于Java包的层次只有一层,所以Java只能直接访问global namespace中的定义,任何其它包中的定义,必须通过import才能够访问。

毫无疑问,C++/C#的方案更加强大灵活,但也更复杂。而复杂的东西往往让使用者更容易犯错误。孰优孰劣,你自己判断吧。

java中的“包”与C#中的“命名空间的更多相关文章

  1. idea在src/main/java下新建包后项目中只显示src/main,后面的东西不显示,但在本地磁盘中是存在的

    去掉图中的勾

  2. java 执行 jar 包中的 main 方法

    java 执行 jar 包中的 main 方法 通过 OneJar 或 Maven 打包后 jar 文件,用命令: java -jar ****.jar执行后总是运行指定的主方法,如果 jar 中有多 ...

  3. hadoop+javaWeb的开发中遇到包冲突问题(java.lang.VerifyError)

    1.HDFS + WEB 项目 报java.lang.VerifyError... 异常 抛异常: Exception in thread "main" java.lang.Ver ...

  4. java中的包以及内部类的介绍

    1:形式参数和返回值的问题(理解)    (1)形式参数:        类名:需要该类的对象        抽象类名:需要该类的子类对象        接口名:需要该接口的实现类对象    (2)返 ...

  5. java编程思想第四版中net.mindview.util包下载,及源码简单导入使用

    在java编程思想第四版中需要使用net.mindview.util包,大家可以直接到http://www.mindviewinc.com/TIJ4/CodeInstructions.html 去下载 ...

  6. java中的包有那些 ???

    java.util工具包java.sql数据库包java.io输入输出流包java.net网络包java.lang基础包这些是基本的包,还有一些其他的例如集合,反射等的工具包,你可以去查一下java ...

  7. Java中的包

    包:定义包用package关键字. 1:对类文件进行分类管理. 2:给类文件提供多层名称空间. 如果生成的包不在当前目录下,需要最好执行classpath,将包所在父目录定义到classpath变量中 ...

  8. (转)java 从jar包中读取资源文件

    (转)java 从jar包中读取资源文件 博客分类: java   源自:http://blog.csdn.net/b_h_l/article/details/7767829 在代码中读取一些资源文件 ...

  9. JAVA中反射机制五(java.lang.reflect包)

    一.简介 java.lang.reflect包提供了用于获取类和对象的反射信息的类和接口.反射API允许对程序访问有关加载类的字段,方法和构造函数的信息进行编程访问.它允许在安全限制内使用反射的字段, ...

随机推荐

  1. jquery图片滚动

    注:代码来自17sucai网,已去除部分冗余代码,只保留图片效果 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional// ...

  2. 深入探讨 java.lang.ref 包--转

    概述 Java.lang.ref 是 Java 类库中比较特殊的一个包,它提供了与 Java 垃圾回收器密切相关的引用类.这些引用类对象可以指向其它对象,但它们不同于一般的引用,因为它们的存在并不防碍 ...

  3. [转] IPC之管道、FIFO、socketpair

    管道和FIFO作为最初的UNIX IPC形式,现在已用得较少.SocketPair可作为全双工版本的管道,较为常用,这里简单做个笔记 管道 * 只用于有亲缘关系的进程间通信 * 单向,即半双工 (双向 ...

  4. Linux shell入门基础(一)

    Linux shell入门基础(一): 01.增加删除用户: #useradd byf   userdel byf(主目录未删除)  userdel -r byf   该用户的属性:usermod 用 ...

  5. Avast注册以及更新

    昨天无聊在网上看Avast,然后下了玩. Avast有三种版本,免费版,网络版,高级版. 下了个高级版,在贴吧和论坛找激活码,发现大部分都没用,最后找了一个许可文件 关于Avast的注册,有在线和离线 ...

  6. hdu 2304

    题意: 插座插空问题 水题.....只要知道最后一个不需要插即可.... 直接贴代码.. AC代码: #include <iostream> using namespace std; in ...

  7. Hibernate 关联查询 相关错误

    错误提示: could not resolve property: 确定有相关属性时,记得使用 criteria.createAlias @ManyToOne 若可能为null 要加上 @NotFou ...

  8. java的@see注释

    @see注释用法 @see 类名 @see #方法名或属性名 @see 类名#方法名或属性名

  9. 在SPItemEventReceiver中使用BeforeProperties和AfterProperties

    当你利用这些事件时,就很快会发现存在前(同步)后(异步)两种事件.其方法的后缀分别为“ing”(比如,ItemAdding)和“ed”(比如,ItemAdded),分别代表了变更发生前调用和发生后调用 ...

  10. 没有找到iertutil.dll怎么办?快速解决iertutil.dll丢失

    iertutil.dll 是存放在 C:\Windows\System32 目录下的一个动态链接库文件,它提供函数给其他程序所调用.iertutil.dll 能够实现接到互联网,纪录输入,监控应用程序 ...