在 PropertyResourceBundles 中引入继承
使用 PropertyResourceBundle 创建一个完全国际化的 Java 应用程序可以引出一些有趣的设计和实现问题,包括有关如何将束模块化以便在应用程序的不同领域使用。在本文中,我们将研究一种基于 PropertyResourceBundle 的解决方案,它将在促进现有束的重用的同时,简化设计和实现的问题。
PropertyResourceBundle 是一种 Java 机制,用于从实际的 Java 代码中分离出特定于语言环境的文本。当应用程序调用这些特定于语言环境的其中一个属性时,与给定的用户语言环境相关的一个文本文件就被打开并被阅读。
从 Java 代码中分离出对语言环境敏感的消息是处理国际化问题的关键。只要应用程序使用 PropertyResourceBundle 来获取它特定于语言环境的属性,就可以通过为所有受支持的语言环境提供属性文本文件而将应用程序很容易地翻译成任何语言。(请参阅 参考资料,了解有关国际化的一些优秀文章。)
![]() |
|
维护大量的资源束会是一项十分艰巨的任务 ― 就象努力维护一大段代码一样令人生畏。那么为什么不应用一些在代码中使用的原理来促进资源束的重用呢?
本文的重点是为 PropertyResourceBundle 引入继承概念。以这种方式,公共束就可以被共享,而可以在不影响其它的束用户的前提下,引入更多特定的束。
首先,我将概括地叙述一下最普遍使用的设计策略。
当前使用 PropertyResourceBundle 的设计通常遵循以下三个策略:
- 使用一个大的、囊括一切的束(所有应用程序组件都共享这个束)。
- 允许每一个应用程序组件拥有自己独立的束,这些束不在应用程序间重用。
- 允许一些不具有重复属性定义的资源束。
让我们更详细地看一下其中的每一种策略:
第一种方法在有关设计时和运行时的资源束上都有很大的争议。它并不支持重用,因为只存在一个束且对该束所做的任何更改都会影响到使用该束的其它组件。
这种方法中的任何属性的重新定义都要求所有组件使用新版本,或者添加一个新属性。无论如何,维护大量资源束会很快变得很吃力,而且消耗的时间比您所希望的要多。
第一种方法的另一种选择是将单资源束分裂成原始束的一些子集。这种方法将使每一个束独立于其它束;允许每一个束重新定义属性以供自己使用。
这种设计不支持重用,因为每一个束都必须定义它自己的属性。而且每一个束在其定义中都保留独立性。
各个束之间保持一致现在变得更困难,因为每一个束都是独立的。比如每一个束可能都定义一个带有邮件地址的属性。如果这个地址现在发生了更改,每一个与此邮件地址有关的束都必须改变。
第三种方法依赖于代码开发人员要明确了解给定任何属性时要转向哪一个资源束。
使 用邮件地址属性示例(其中地址属性只在一个资源束中定义)时,任何需要包含地址的应用程序组件都必须转到定义地址的特定资源束。在这种模式下,应用程序组 件必须确定给定一个键时,要转到哪一个资源束。对资源束所做的任何更改都可能要求对使用它们的应用程序组件进行类似的更改。
使用这种方法时,维护问题真的刚好从资源束转到获取属性的代码上了。
为了寻求一个有效的解决方案,我们需要在这三种方法中权衡利弊。
![]() |
|
![]() ![]() |
![]()
|
最好的解决方案应该可以最大限度地减少上面讨论的方法中的缺点,同时具有每种方法中的优点。在 Java PropertyResourceBundle 现有的国际化能力中引入继承,是解决这个问题的一种方法。
这 种策略将最大限度地重用束,同时给予每一个应用程序组件足够的自由,让其为自己定义新的属性或者重新定义现有的属性。这种概念建立在现有的用于定义国际化 束的 Java 功能上,这种国际化束可以转入和转出给定的语言环境。按照这种方法构建,您不必复制任何功能;软件会简单地重用提供的功能。
束可以在整个应用程序组件间重用,因为它们可以通过层次结构的方式被定义。组件可以选择定义一些新属性在组件中使用,同时从另一个束中继承其它属性。一个属性只需定义一次。其它组件可以简单地继承这个属性。
另外一种选择是,一个组件可以决定继承的属性不适合自己使用,然后重新定义该属性 ― 这种概念类似于面向对象编程中的覆盖方法或属性。
增加继承还减少了维护工作,因为每个属性都被定义在层次结构中明确的位置上。如图 1 所示,被更改的属性会影响从发生更改的束继承而来的所有的束。
图 1. 资源束继承层次结构的示例
![图 1. 资源束继承结构的示例](https://i-blog.csdnimg.cn/blog_migrate/f14d708f85e7a169a06fe130f6e06a45.png)
![]() ![]() |
![]()
|
为实现我们的策略,首先我们需要设计继承的机制。在设计机制时要牢记,我们要设法尽量减少现有代码和现有资源束的必需的更改。
为了使现有代码使用继承束,我们需要合并一些最小限度的更改,包括从 PropertyResourceBundle 到新的对象类型的转换并使用一个新的 管理器类(稍后我会讨论这个类)。
现有的资源束需要重新定义吗?不。这种方法的一个显著的副作用就是,现有的资源束可以变成整个继承结构的基础,而无须对这些资源束进行改变。现有的资源束可以按原样使用。
另一个设计目的是为了能够在代码 内和代码 外(在将列出一个给定束的关系的另一个文件中)都能定义束关系层次结构。这种方法为您提供了在代码外定义关系的机会,这样会使修改和维护更加容易。也可以在代码内改变层次结构,这样就可以在运行时为开发人员提供精细的控制。
![]() ![]() |
![]()
|
新实现的主要功能是提供描述束之间的关系的继承机制。
与 Java 语言的 类继承不同,每个新束都被允许从多个父束中逻辑地继承属性。
新类:InheritedPropertyResourceBundle
为实现这些思想,我们将创建一个继承 java.util.ResourceBundle 类的新类。新类将保持对 PropertyResourceBundle 的单独引用,还有对任意数量的父束(被 InheritedPropertyResourceBundle 的这个实例逻辑地继承的束 ― 请参阅清单 1)的可选的引用。
清单 1. InheritedPropertyResourceBundle 类的开头
/** |
注意:请参阅 参考资料下载本文中使用的完整的源代码。
如清单 2 所示,当 InheritedPropertyResourceBundle 按照给定的键查询时,将首先检查提供支持的 PropertyResourceBundle 来查找资源。
清单 2. 用给定的键检查 PropertyResourceBundle
Object retVal = null; |
如果提供支持的 PropertyResourceBundle 找不到资源,将按顺序检查父束,如清单 3 所示。
清单 3. 用给定的键检查父束
if (retVal == null) { |
我们的另一个设计目的是允许在代码内和代码外(在一个独立的文件中)都能修改关系层次结构。(稍后我们会讨论第二个问题。)
如清单 4 所示,为了允许改变层次结构,提供了一套修改器方法。这些方法允许在运行时添加和移去父束。
清单 4. 关系层次结构修改器方法
/** |
我们创建了另一个类来处理从文件中载入父束的细节问题。我们这样做是为了从 InheritedPropertyResourceBundle 的实现中分离出对平面文件进行语法分析的逻辑。
新类 RelationshipLoader 既处理文件位置逻辑,也实际解析文件。
RelationshipLoader 类寻找一个与 InheritedPropertyResourceBundle 同名,但带有 .relationships 后缀的文件。例如,如果一个束是用基础束名称 resources.ProductNames 创建的, RelationshipLoader 类就会按照类装载程序管理的搜索规则来搜索一个名为 resources/ProductNames.relationships 的文件。
因为关系是定义在全局级别上,而且与使用的语言环境无关,.relationships 文件不需要翻译。
.relationships 文件最好与实际的 .properties 文件位于同一目录下,所以全部的资源束文件都保存在明确的位置。如果 RelationshipLoader 没有找到 .relationships 的资源束文件,它就假定束不是从任何父束中继承下来的。它会和 PropertyResourceBundle 一样运行。请参阅清单 5。
从实际的资源束中分离继承关系定义允许当前所有的资源束按原样使用 ― 在这种新模式下,不需任何修改就可以使用现有的资源束。
清单 5. 在 RelationshipLoader 中用关系文件初始化父束
/** |
最后一个新类是所有创建的 InheritedPropertyResourceBundle 的一个管理器类,名为 InheritedBundleManager 。 InheritedBundleManager 类是单元素 ― 在 Java 虚拟机中只有一个实例是有效的。使用这个管理器类的原因是要最大限度地减少内存中的资源束。这样考虑一下:如果另一个束每次继承父束时,都要创建一个新的父束,VM 中资源束的数量很快就会超出控制。
出于这个原因, InheritedBundleManager 类管理由 InheritedPropertyResourceBundle 创建的所有实例。每一个束名称、语言环境和类装载程序(语言环境和类装载程序作为可选参数)只允许有一个 InheritedPropertyResourceBundle 的实例。
例如,第一次用缺省语言环境和类装载程序请求 resources.ProductNames 束时,管理器会创建一个新的实例并将其放入缓冲区。当出现带有相同参数的后继请求时,缓冲的实例就会被返回。
所有的新代码都通过 InheritedBundleManager 来获取 InheritedPropertyResourceBundle 的一个实例。重要的是继承束机制的用户要做同样的事。
![]() ![]() |
![]()
|
这种方法并非万灵药;它的缺点包括循环继承、获取资源束引用的问题和继承了意外的类。让我们更详细地看一下其中的每一个缺陷。
当前的实现并不检查循环继承。例如,如果定义了两个 InheritedPropertyResourceBundle ― 一个名为 resources.CompetitorProductNames ,另一个名为 resources.ProductName ― 两者都被定义为逻辑地继承另一个,解决方案就会失败。
这种检查被有意地留在了设计之外,因为它会降低性能,特别是当继承树比较大的时候。
如果清楚地定义了继承层次结构,确保没有形成循环继承的相关问题,这个问题完全可以被消除。 可以向代码添加检查语句,如果添加的父束会引起循环继承有关的问题,那么一个异常将被抛出。因为所有的父束都是通过 InheritedPropertyResourceBundle 中的两个 addRelationship() 方法之一添加的,可以在那里添加检查语句。
获取对资源束的引用的通常方法 ― 通过 ResourceBundle.getBundle() 方法 ― 不能在这种方法中使用,因为这些静态的方法是硬编码以返回一个特定的类型。换句话说,没有办法嵌入这种机制从而令人满意地返回一个新 InheritedPropertyResourceBundle 类的实例。
要获取对 InheritedPropertyResourceBundle 的一个引用,客户机必须通过 InheritedBundleManager 接口。因此,您可能需要改变现有应用程序中的一些代码。
InheritedPropertyResourceBundle 类的实现继承 java.util.ResourceBundle 而不是 java.util.PropertyResourceBundle 。
为什么? PropertyResourceBundle 的创建是与 ResourceBundle.getBundle() 方法紧密结合在一起的。如果实现反而继承了 PropertyResourceBundle , ResourceBundle.getBundle() 中的大量现有逻辑将需要被管理器类重复。这就是为什么必须更改一些代码的另一个原因。
如果客户机代码需要 java.util.ResourceBundle 类型,这种解决方案不需要修改就可以很好地工作。另一方面,如果客户机代码需要 java.util.PropertyResourceBundle ,代码就需要修改了。
![]() ![]() |
![]()
|
继承方法有助于减轻与国际化开发工作有关的一些麻烦:
- 我们可以保留与资源束相关的当前功能,同时添加一些继承语义。我们的方法允许编写单独的资源束来配合指定的应用程序,同时还允许用户重用其它资源束来尽量避免资源重复。
- 由于在继承层次结构的较高级别上对资源所做的更改会在较低的级别中反映出来,维护工作减轻了。
- 最后,我们最大限度地减少了对源代码的更改,同时完全消除了修改现有资源束的需要。要将现有的资源束转换成“继承的”版本,只需在客户机代码中简单地添加一个关系文件,或定义一个父束。
使用这里描述的方法开发完全国际化的 Java 应用程序可以在很大程度上简化您的设计和实现工作,同时使重用现有的束更加容易。
来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/374079/viewspace-130243/,如需转载,请注明出处,否则将追究法律责任。
转载于:http://blog.itpub.net/374079/viewspace-130243/