Java包级别作用域有用吗?


11

我了解包范围的概念,有时甚至认为我想要它。但是,每次我认真地尝试使用它时,我发现它都不符合我认为可以满足的需求。

我的主要问题始终似乎是,我希望限制范围的东西从来不在同一包中。它们可能在概念上全部链接在一起,但是应用程序中数据的逻辑划分将它们作为较大包的单独子包。

例如,我可能有一个Mission模型,并且我只希望其他Mission工具(例如我的missionServices)使用某些方法。但是,我最终将Missions.models和Missions.services作为我的包,所以MissionModel和MissionService不在同一包范围内。似乎从来没有出现过这样的情况:程序包适当地包含了我希望具有提升的权限的内容,却又不包含我不希望拥有这些权限的许多内容;我几乎没有感觉到包范围界定方法的优势证明修改我的项目体系结构以将所有内容都放在同一包中是合理的。通常,无论是Aspect还是某种形式的控制反转,都是解决我简要考虑过的程序包作用域的任何问题的更好方法。

我很好奇,这在所有Java开发人员中通常被认为是正确的,或者只是我所做的工作的fl幸。封装范围在现实世界中使用率很高吗?在很多情况下,它被认为是很好的使用形式,还是在大多数情况下,它被视为遗留行为而很少被开发利用?

我没有问任何关于为什么包私有作用域是默认值的问题,我在问什么时候应该使用它而不考虑默认值。关于为什么默认为默认值的大多数讨论并没有真正进入包范围实际有用的时间,而只是在争论为什么不应该将其他两个常用范围设为默认值,从而使包在消除过程中获胜。另外,我的问题是关于当前的发展状况。具体来说,我们是否已经开发到其他工具和范例使得程序包作用域不那么有用的地步,而当它成为默认值的决定才有意义时。


思想实验:如果没有软件包,Java程序会是什么样?可能是您没有看过任何大型 Java程序,也没有从事任何大型 Java程序的工作。
罗伯特·哈维

看的代码java.util.Stringjava.util.Integer


2
重复问题上投票最多的答案涵盖了package-private的最重要用法,隐式回答了为什么有用。

2
我完全不相信重复。这个问题问“包装范围是什么?” 另一个问(除其他事项外)“给定包范围是默认范围,私有范围是做什么的?” 再加上对骗子的接受答案和此处接受的答案提出了完全不同的观点。
Ixrec

Answers:


2

在整个java.util.*程序包中,有时会编写带有程序包级别保护的代码。例如,这是Java 6中java.util.String- 构造函数

// Package private constructor which shares value array for speed.
String(int offset, int count, char value[]) {
    this.value = value;
    this.offset = offset;
    this.count = count;
}

或此getChars方法

/** Copy characters from this string into dst starting at dstBegin. 
    This method doesn't perform any range checking. */
void getChars(char dst[], int dstBegin) {
    System.arraycopy(value, offset, dst, dstBegin, count);
}

这样做的原因是代码的设计人员(java.util.*可以认为是一个很大的库)希望具有以牺牲各种安全性为代价(对数组进行范围检查,直接访问其他字段)的更快性能的能力。表示实现而不是接口,是对类中某些部分的“不安全”访问,否则这些部分将被视为通过公共方法是不可变的)。

通过将这些方法和字段限制为仅可由java.util包中的其他类访问,这可以使它们之间更紧密地耦合,但也避免了将这些实现细节公开给外界。

如果您正在处理某个应用程序而不必担心其他用户,则不必担心软件包级别的保护。除了设计纯度的各个方面之外,您还可以制作所有东西,public并可以做到这一点。

但是,如果您正在使用一个供其他人使用的库(或者想要避免过多地缠绕类以便以后进行重构),则应使用为您的需求提供的最严格的保护级别。通常这意味着private。但是,有时候,您需要向同一包中的其他类公开一些实现细节,以避免重复您自己或使实际实现更容易一些,但要以牺牲两个类及其实现的耦合为代价。从而,对包装进行保护。


1
我确实通过java.util查看,并且看到了。但是,我也注意到这是一个大型库。如今看来,如果不使用子包,很少会写出这么大的库。那些包装限制导致了限制。我并不是说java.util是错误的,但是似乎他们可以摆脱大型软件包的原因仅仅是因为他们拥有极其优秀的编程人员,他们可以相信通过封装在有限的封装中就能正确地完成所有事情。我会赤裸裸地相信,对于那些可能会开发与我相似的程序包的普通开发人员来说,这样做有很大的潜力。
dsollen 2015年

1
@dsollen如果您深入研究Spring,Hibernate或类似的大型库,您会发现类似的东西。有时您需要更紧密的耦合。一个人应该有机会对提交的内容进行代码审查,并确保一切正确。如果您不需要更紧密的耦合,或者根本不信任其他编码器,那么公共字段,包或受保护的字段或方法并不总是合适的。但是,这并不意味着它没有用。

简而言之,我很想制造更多受限制的软件包,而不用担心满足我日常需求的优化水平(这在95%的项目中只是浪费)。因此,我的跟进问题就变成了包级范围是仅在某些广泛使用的大规模API中使用的非常具体的优化潜力吗?IE只有伟人会需要或想要放松保护到那个程度?是否有理由让优秀的程序员甚至使用较小的用户群来开发“标准” API都可以找到用处?
dsollen 2015年

我没有足够快地跟进我的跟进工作:)我理解并看到你的观点,我根本没有在辩论。更多的只是提出一个后续问题,即它对我们其他优秀程序员的有用性,但对于那些如此庞大的API却无法使用。特别是,它主要是在牺牲封装权衡的情况下进行的优化,仅在确实需要调整性能时才进行。
dsollen 2015年

2
@dsollen与性能无关(尽管这是公开实现的原因之一),而是封装。您这样做是为了避免重复代码,Integer.toString()并且String.valueOf(int)可以共享代码而又不会将其公开。这些java.util类将协同工作以提供更大的整体功能,而不是作为单独的类,而这些类完全是它们自己的孤岛-它们一起放在一个包中,并通过包级作用域共享实现功能。

5

有时,我会升级私有方法或受保护方法的可见性以进行打包,以使junit-test可以访问一些不应从外部访问的实现细节。

由于junit-test与被测项目具有相同的软件包,因此可以访问这些实现细节

  public class MyClass {
    public MyClass() {
        this(new MyDependency());
    }

    /* package level to allow junit-tests to insert mock/fake implementations */ 
    MyClass(IDependency someDepenency) {...}
  }

这是否是“好”用法还是值得商but的,但这是实用的


2
我总是将测试放在不同的程序包中,否则我发现我在默认范围内留下了一些关键方法,该方法不允许其他人调用它
soru 2015年

我从不使用方法来执行此操作,但是它是用于注入依赖项的好功能。例如,在测试EJB时,可以通过使用Mock对象设置程序包级别的EM来模拟注入的EntityManager,该对象在运行时由应用程序服务器注入。
jwenting 2015年

为什么要提升protected到package-scope? protected 已经提供软件包访问权限。这有点古怪,但是我认为他们不想要求复合范围声明,例如protected packagescope void doSomething()(也需要一个new关键字)。
tgm1024--Monica在

2

在人们倾向于使用包的点缀名称(好像它们是子包)的世界中,这并不是那么有用,尤其是作为默认值。因为Java没有子包之类的东西,所以xyinternals对xy的访问权限比对ab的访问权限更大。包名称相同或不同,部分匹配与没有共同之处没有什么不同。

我想最初的开发人员从来没有期望使用该约定,而是想保持简单而不增加Ada样式的分层程序包的复杂性。

Java 9可以解决这个问题,因为您可以在一个模块中放入几个软件包,而仅导出其中一些。但这将使包范围变得更加多余。

在理想情况下,他们会将默认范围更改为“模块范围(如果存在包含模块),否则为包范围”。然后,您可以将其用于在模块内共享实现,从而允许重构而不会破坏导出的接口。但是,也许有一些微妙之处,为什么这会破坏向后兼容性,否则会变得不好。


我发现Java 9注释很有趣。我认为一种简单的识别包herachies的能力以及以某种方式定义相对于某些父包的包范围(使用批注?)会很好。
dsollen 2015年

1
@dsollen,也许,但我的第一个倾向是,我们将开始用那个铁镀金。在我看来,更好的第一步(在一定程度上带来安全性)将是发明一个实际的包装范围关键字。它甚至可能是“包” :)
tgm1024-Monica在

2

类型的凝聚力你的描述是不是真的,在那里你会使用包访问最好的例子。包很方便用于创建可在接口上互换的组件和模块。

这是场景的一个粗略示例。假设您的代码经过重组,使其更多地围绕功能内聚和组件化。也许3个罐子像:

**MissionManager.jar** - an application which uses the Java Service Provider Interface to load some implementation of missions, possible provided by a 3rd party vendor.

**missions-api.jar** - All interfaces, for services and model
    com...missions
        MissionBuilder.java   - has a createMission method
        Mission.java - interface for a mission domain object
    com...missions.strategy
        ... interfaces that attach strategies to missions ...
    com...missions.reports
        .... interfaces todo with mission reports ....

   **MCo-missionizer-5000.jar** -  provides MCo's Missionizer 5000 brand mission implementation.
       com...missions.mco
                  Mission5000.java - implements the Mission interface.
       com...missions.strategy.mco
              ... strategy stuff etc ...

通过使用Mission5000.java中的程序包级别,您可以确保没有其他程序包或组件(如MissionManager)可以与任务实现紧密结合。只是“ M co”提供的“第三方”组件实际上在其中创建了自己的特殊品牌任务实施。任务管理器必须调用MissionBuiler.createMission(...)并使用定义的接口。

这样,如果替换了Mission实施组件,我们知道MissionManager.jar的实施者不会绕过api并直接使用MCo的实施。

因此,我们可以将MCo-missionizer5000.jar替换为竞争产品。(或者是一个用于对任务管理器进行单元测试的模拟程序)

相反,MCo可以掩盖其专有的传教士商业秘密。

(这与在组件中强制执行“ 不稳定性和抽象性”思想有关。)


-1

由于Java中的包作用域不是以分层方式设计的,因此几乎没有用处。我们根本不使用包作用域,而是将所有类定义为public。

相反,我们基于包层次结构和预定义的包名称使用我们自己的访问规则。

如果您对详细信息感兴趣,请使用Google的“ CODERU规则”。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.