大型内部类和私有变量

我经历过几次的一件事是服务类(比如JBoss服务)由于帮助器内部类而变得过大。 我还没有找到一个打破课堂的好方法。 这些助手通常是线程。 这是一个例子:

/** Asset service keeps track of the metadata about assets that live on other * systems. Complications include the fact the assets have a lifecycle and their * physical representation lives on other systems that have to be polled to find * out if the Asset is still there. */ public class AssetService { //...various private variables //...various methods public AssetService() { Job pollerJob = jobService.schedule( new AssetPoller() ); Job lifeCycleJob = jobService.schedule( AssetLifecycleMonitor() ); } class AssetPoller { public void run() { // contact remote systems and update this service's private variables that // track the assets. } } class AssetLifecycleMonitor { public void run() { // look for assets that have meet criteria for a lifecycle shift // and update this service's private variables as relevant. } } } 

那么,如果我有几个帮助器并且它们都很复杂会发生什么,那么整个类文件会变得非常大。 我喜欢内部类,因为它表明这些类完全由服务拥有,并且仅存在于帮助该服务。 我试过打破课程并将父服务作为参考传递,这主要起作用,但我不喜欢的是:

  • 我最终暴露了包级访问器,因此分解的类可以获取变量,而在我没有公开setter之前,因为内部类具有直接访问权限。
  • 此外,事情变得更加冗长,因为我经常调用访问器而不是底层变量。 一个小的,被授予。
  • 方便方法(例如checkAssetIsValid()或其他一些方法)现在需要包级别暴露,因此帮助程序类可以调用它们,而在内部类中它们可以是私有的。
  • 更糟糕的是,我需要将服务实现类传递给辅助类构造函数,因为我不希望在服务实现的接口中公开这些帮助器方法,因为这会强制它们是公共的。 这可能会产生一些unit testing/模拟问题。
  • 更糟糕的是,我想要做的任何同步都会通过一些外部便捷方法(例如在轮询器更新期间的lockDownAssets())泄露出来。 之前,内部类可以访问私有锁。
  • 所以,简而言之,打破课程会失去一些我喜欢的封装。 但是将它们留在可以导致一些大的java文件。 我还没有找到解决这个问题的好方法。 C ++有“朋友”的概念,我很少想念,但在这种情况下实际上会有所帮助。

    思考?

    在字节码级别,内部类只是普通的Java类。 由于Java字节码validation程序不允许访问私有成员,因此它会为您使用的每个私有字段生成合成访问器方法。 此外,为了将内部类与其封闭实例链接,编译器将合成指针添加到外部“this”。

    考虑到这一点,内部类只是一层语法糖。 它们很方便,你列出了一些优点,所以我列出了一些你可能想要考虑的消极方面:

    • 您的内部类对整个父类具有隐藏的依赖关系,这会对其入站接口进行模糊处理。 如果将其解压缩为包私有类,则有机会改进设计并使其更易于维护。 最初它更冗长,但通常你会发现:
      • 您实际上想要共享一个值对象,而不是公开10个访问者。 通常你会发现你并不需要引用整个外部类。 这也适用于IoC。
      • 它不是提供显式锁定的方法,而是将操作与其上下文封装在一个单独的类中(或将其移动到两个类中的一个 – 外部或以前内部),这样更易​​于维护。
      • 便捷方法属于包私有实用程序类。 您可以使用Java5静态导入使它们显示为本地。
    • 您的外部类可以绕过任何保护级别并直接访问内部类的私有成员。 这本身并不坏,但它消除了表达你的设计的语言手段之一。
    • 由于您的内部类只嵌入在一个外部类中,因此重用它的唯一方法是子类外部类。 另一种方法是将显式引用传递给外部类实现的包私有接口。 这将允许您模拟外部并更好地测试内部类。
    • 虽然最近的调试器非常好,但我在调试内部类之前遇到了问题(条件断点范围混乱,没有在断点处停止等)
    • 私有类膨胀你的字节码。 请参阅我的第一段 – 通常有一个API可供您使用并减少合成残骸的数量。

    PS我在谈论非平凡的内部类(特别是那些没有实现任何接口的内部类)。 三线监听器实现很好。

    不要忘记考虑为什么要分手你的大class。 它是用于软件工程目的吗? 例如,它是一个编程热点,你有这么大的文件导致开发团队的复杂合并?

    这只是避免大class的一般愿望吗? 在这种情况下,您可能会花更多时间来改进您所拥有的代码。

    代码变得难以管理,例如调试和确保避免意外的副作用变得更加困难。

    Rick关于使用unit testing来确保持续一致行为的评论是非常有价值的,也是一个好主意。 可能是当前的设计只是简化了重构,你最好从原始界面开始重新实现相同的行为。 准备好进行大量的回归测试!

    封装和分离之间的界限可能难以行走。 但是,我认为这里的主要问题是您需要某种可靠的交互模型作为分离类的基础。

    我认为在许多地方使用外部帮助器实用程序类是合理的,只要它们没有副作用我没有看到问题。 拥有静态帮助程序类也是合理的,只要它们组织良好,它们包含常用的方法,如checkAssetIsValid()。 这假设checkAssetIsValid不需要访问除传递它的对象之外的任何外部状态。

    分离最重要的事情不是拥有在许多这些类中共享永久引用的对象。 我喜欢看function性编程以获得指导。 每个class级都不应该达到其他class级的内涵和改变状态。 相反,每个工作的类都应该生成和使用容器对象。

    可视化也非常有用。 我在这里注意到了一个关于Java Visualization工具主题的线程。 理想情况下,您的类交互图应该看起来更像树而不是图。

    另外,我只想指出将大型类重构为较小的类可能非常困难。 最好至少为公共接口构建一套unit testing,以便在你破坏某些东西时立即变得明显。 我知道过去的测试为我节省了无数个小时。

    希望其中一些会有所帮助。 我只是在这里闲聊。

    我不喜欢过度使用内部类。 我认为他们并没有真正提供任何优势(当极端使用时)将代码放在普通类中不会,并且它们只是用于使类文件不必要地大而且难以遵循。

    如果必须提高几种方法的可见性,有什么危害? 是否会完全打破你的抽象或界面? 我认为程序员往往会默认将所有内容设为私有,其中一些其他类调用您的方法并没有太大的危害 – 如果您的设计是真正基于OO的,那就是。

    如果所有“内部帮助程序类”都需要访问某些相同的方法,请考虑将它们放在基类中,以便可以通过inheritance来共享它们。

    叶氏。 可能你需要重新重构那些助手而不是按原样移动它们。 有些东西属于帮助者的其他服务。 应该使用可能的新类来封装数据。

    您可以使用的一个可能性是AOP提供细粒度访问,并且在切入点中包括该方法应该仅从“朋友”类调用。 仍然你的方法将暴露:(

    我想这没有简单的解决方案。