• java中的“包”与C#中的“命名空间


    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#的方案更加强大灵活,但也更复杂。而复杂的东西往往让使用者更容易犯错误。孰优孰劣,你自己判断吧。

  • 相关阅读:
    JeeSite4.x 搭建并部署到服务器
    maven编译时出现There are test failures
    ecplise An incompatible version [1.2.14] of the APR based Apache Tomcat Native library is installed, while T
    maven "mvn不是内部或外部命令,也不是可运行的程序或批处理文件"
    rar自动压缩备份
    mysql 0x80004005 unable to connect to any of the specified mysql hosts
    mysql too many connections
    输出控制台信息到日志 并 通过cronolog对tomcat进行日志切分
    Node.js相关——package概念及NPM
    Node.js相关——CommonJS规范
  • 原文地址:https://www.cnblogs.com/lidabo/p/2819865.html
Copyright © 2020-2023  润新知