Java模式设计之多态模式与多语言支持
副标题#e#
作为工具的建设模式,多态模式中的多态类可有多个实例;并且多态类必需本身建设、打点本身的实例,并向外界提供本身的实例。读者在阅读本文的时候,可以参考阅读笔者的《Java与模式》一书(刚由电子家产出书社出书)中的相关章节。
引言 一个真实的项目
这是一个真实的、面向全球消费者的华尔街金融网站项目标一部份。凭据项目打算书,这个网站系统是要由数据库驱动的,而且要支持十九种差异的语言;并且在未来支持更多的语言。消费者在登录到系统上时可以选择本身所需要的语言,系统则按照用户的选择将网站的静态文字和动态文字全部转换为用户所选择的语言。
颠末接头,设计师们同意对静态文字和动态文字采纳差异的办理方案:
把所有的网页交给翻译公司对上面的静态文字举办翻译, 而网页上面的动态内容则需要措施办理。
在举办了研究后,设计师们发明,他们需要办理的动态文字的“翻译”问题,实际上是将数据库中的一些静态可能半静态的数据举办“翻译”。下面就是一个典范的数据表:
钱币代码 | 钱币名称 | 钱币尾数 |
USD | America (United States of America), Dollars | 2 |
CNY | China, Yuan Renminbi | 2 |
EUR | France, Euro | 2 |
JPY | Japan, Yen | 0 |
代码清单1、为英文用户的筹备的钱币列表。
钱币代码永远是上面所看到的英文代码,可是钱币名称该当按照用户所选择的语言差异而差异。好比对中文读者就该当翻译成为下面的表:
钱币代码 | 钱币名称 | 钱币尾数 |
USD | 美国 (美利坚合众国), 美元 | 2 |
CNY | 中国,人民币元 | 2 |
EUR | 法国, 欧元 | 2 |
JPY | 日本, 日元 | 0 |
代码清单2、为中文用户筹备的钱币列表。
这样的表会在网页上作为下拉菜单呈现,用户看到的是钱币名称,而系统内部利用的是钱币代码。
#p#副标题#e#
国际化办理方案
这样的问题就是国际化的问题,所谓国际化就是Internationalization,简称作i18n(请拜见本章最后的问答题)。
设计师所采纳的实际方案是分层方案,也就是MVC模式。MVC模式将系统分为三个条理,也就是模子(Model)、视图(View)、节制器(Control)三个部份。国际化是视图部份的问题,因此该当在视图部份获得办理。
图1、MVC模式的示意图。
换言之,系统的内核可以是纯英文的;在内核外部增加一个壳层认真语言翻译事情。请见下面的观念图:
图2、英文内核和翻译壳层的观念图。
所谓内核就是系统的模子,而翻译壳层即是视图的一部份。对多语言的支持属于视图成果,因此不应当在内核办理,而该当在视图办理。这就是设计师们告竣的总体方案。
多态模式 多态模式的特点
所谓的多态模式(Multiton Pattern),实际上就是单态模式的自然推广。作为工具的建设模式,多态模式或多态类有以下的特点:
多态类可有多个实例; 多态类必需本身建设、打点本身的实例,并向外界提供本身的实例。
单态类一般环境下最多只可以有一个实例,请见下面的布局图:
图3、单态类的布局图。
可是单态模式的精力是答允有限个实例,并不是仅答允一个实例;这种最多只答允有限多个实例,并向整个JVM提供本身实例的类叫做多态类(Multiton),这种模式叫做多态模式(Multiton Pattern),请拜见下面的布局图。
图4、多态类的布局图。
本章就需要用多态模式来实现资源工具,需要结构出能提供有限个实例,每个实例有各不沟通的属性(即Locale代码)。
有上限多态类
一个实例数目有上限的多态类已经把实例的上限看成逻辑的一部份制作到了多态类的内部;这种多态模式叫做有上限多态模式。
好比每一麻将牌局都需要两个色子,因此色子就该当是双态类。这里就以这个系统为例,说明多态模式的布局。
图5、色子的类图。
下面就是多态类Die(色子)的源代码:
#p#分页标题#e#
package com.javapatterns.multilingual.dice;
import java.util.Random;
import java.util.Date;
public class Die
{
private static Die die1 = new Die();
private static Die die2 = new Die();
/**
* 私有的结构子担保外界无法
* 直接将此类实例化
*/
private Die()
{
}
/**
* 工场要领
*/
public static Die getInstance(int whichOne)
{
if (whichOne == 1)
{
return die1;
}
else
{
return die2;
}
}
/**
* 掷色子,返还一个在1到6之间的
* 随机数。
*/
public synchronized int dice()
{
Date d = new Date();
Random r = new Random( d.getTime() );
int value = r.nextInt();
value = Math.abs(value);
value = value % 6;
value += 1;
return value;
}
}
代码清单3、多态类的源代码。
在多态类Die中,利用了饿汉方法建设了两个Die的实例。按照静态工场要领的参数,工场要领返还两个事例中的一个。Die工具的dice()要领代表掷色子,这个要了解返还一个在1到6之间的随机数,相当于色子的点数。
package com.javapatterns.multilingual.dice;
public class Client
{
private static Die die1, die2;
public static void main(String[] args)
{
die1 = Die.getInstance(1);
die2 = Die.getInstance(2);
die1.dice();
die2.dice();
}
}
代码清单4、客户端的源代码。
由于有上限的多态类对实例的数目有上限,因此有上限的多态类在这个上限便是1时,多态类就回到了单态类。因此多态类是单态类的推广,而单态类是多态类的非凡环境。
一个有上限的多态类可以利用静态变量储存所有的实例;出格是在实例数目不多的时候,可以利用一个个的静态变量储存一个个的实例。在数目较多的时候,就需要利用静态聚积压存这些事例。
无上限多态模式
多态类的实例数目并不需要有上限[CAMP02];实例数目没有上限的多态模式就叫做无上限多态模式。
由于没有上限的多态类对实例的数目是没有限制的,因此固然这种多态模式是单态模式的推广,可是这种多态类并不必然可以或许回到单态类。
由于事先不知道要建设几多个实例,因此一定是利用聚积打点所有的实例。本章要接头的多语言支持方案就需要应用到多态模式,关于没有上限的多态模式的实现可以拜见下面的接头。
图6、没有上限的多态模式(左)和有上限的多态模式(右)的类图。个中N就是实例数目标上限。
有状态的和没有状态的多态类
如同单态类可以分成有状态的和没有状态的两种一样,多态类也可以分成有状态的和没有状态的两种。
多态工具的状态假如是可以在加载后改变的,那么这种多态工具叫做可变多态工具(Mutable Singleton);假如多态工具的状态在加载后就不行以改变,那么这种多态工具叫做稳定多态工具(Immutable Singleton)。显然稳定多态类的景象较为简朴,而可变单态类的景象较为巨大。
假如一个系统是成立在诸如EJB和RMI平分手技能之上的,那么多态类有大概会呈现数个实例;因此在这种环境下除非提供有效的协调机制,否则最好不要利用有状态的和可变的单态类,以制止呈近况态不自恰的环境。读者可以参考本书的“单态(Singleton)模式”一章中的相关接头。
多语言项目标设计
由于熟悉了多态模式,系统的设计实际上并不巨大。
语言代码
下面就是几个常见的语言代码:
#p#分页标题#e#
语言代码说明
de | German |
en | English |
fr | French |
ja | Japanese |
jw | Javanese |
ko | Korean |
zh | Chinese |
地域代码
下面就是几个常见的地域代码:
地域代码说明
CN | China |
DE | Germany |
FR | France |
IN | India |
US | United States |
Locale代码
一个 Locale 代码由语言代码和地域代码组合而成,好比:
语言代码 | 地域代码 | Locale代码 | 说明 |
en | US | en_US | 美国英语 |
en | GB | en_GB | 英国英语 |
fr | FR | fr_FR | 法王法语 |
fr | CA | fr_CA | 加拿大法语 |
de | DE | de_DE | 德国德语 |
zh | CH | zh_CH | 简体汉语 |
代码清单3、Locale代码、语言代码和地域代码。
Resource文件及其定名类型
一个Resource文件是一个简朴的文本文件。一个Resource文件的名字是由一个随笔件名和文件的扩展名properties构成,而Resource文件的随笔件名则是Java措施在挪用此文件时利用的文件名。
一个Resource文件和一个普通的properties文件并无本质区别,但Java语言对两者的支持是有区此外。java.util.Properties类不支持多语言,而java.util.ResourceBundle类则支持多语言。
当Locale代码是en_US时,Resource文件的文件名该当是随笔件名加上Locale代码,就是en_US。当Locale代码是zh_CH时,Resource文件的文件名该当是随笔件名加上Locale代码,就是zh_CH。
奈何利用Locale工具和ResourceBundle工具。
那么奈何利用 ResourceBundle 读取一个Resource文件呢?下面就是一个例子:
Locale locale = new Locale("fr","FR"); ResourceBundle res = ResourceBundle.getBundle("shortname",locale);
代码清单4、奈何利用Locale工具和ResourceBundle工具。
在上面的例子内里,res工具会加载一个名为shortname_fr_FR.properties的Resource文件。
系统的设计
这里给出系统的布局图。个中LingualResourceTester是一个示意性的客户端类,而LingualResource是一个多态类。
图7、多态类LingualResource和客户端类的类图布局。
下面就是这个多态类的源代码:
package com.javapatterns.multilingual;
import java.util.HashMap;
import java.util.Locale;
import java.util.ResourceBundle;
public class LingualResource
{
private String language = "en";
private String region = "US";
private String localeCode = "en_US";
private static final String FILE_NAME = "res";
private static HashMap instances =
new HashMap(19);
private Locale locale = null;
private ResourceBundle resourceBundle = null;
private LingualResource lnkLingualResource;
/**
* 私有的结构子担保外界无法直接将此类实例化
*/
private LingualResource(
String language, String region)
{
this.localeCode = language;
this.region = region;
localeCode =
makeLocaleCode(language , region);
locale = new Locale(language, region);
resourceBundle =
ResourceBundle.getBundle(FILE_NAME, locale);
instances.put( makeLocaleCode(language, region) ,
resourceBundle);
}
/**
* 私有的结构子担保外界无法直接将此类实例化
*/
private LingualResource()
{
file://do nothing
}
/**
* 工场要领,返还一个具有指定的内部状态的实例
*/
public synchronized static LingualResource
getInstance(String language, String region)
{
if (instances.containsKey(
makeLocaleCode(language , region )))
{
return (LingualResource) instances.get(
makeLocaleCode(language , region ));
}
else
{
return new
LingualResource(language, region);
}
}
public String getLocaleString(String code)
{
return resourceBundle.getString(code);
}
private static String makeLocaleCode(
String language, String region)
{
return language + "_" + region;
}
}
代码清单5、多态类LingualResource的源代码。个中的makeLocaleCode()是一个帮助性的要领,在传入语言代码和地域代码时,此要领可以返回一个Locale代码。
#p#分页标题#e#
这个多态类的结构子是私有的,因此不能用new要害字来实例化。所有的实例必需通过挪用静态getInstance()要领来获得。在getInstance()要领被挪用时,措施会首先查抄传入的Locale代码是否已经在instances荟萃中存在;假如已经存在,即直接返回它所对应的LingualResource工具,不然就会首先建设一个这个Locale代码所对应的LingualResource工具,将之存入instances荟萃,并返回这个实例。
下面给出一个客户端的源代码:
package com.javapatterns.multilingual;
public class LingualResourceTester
{
public static void main(String[] args)
{
LingualResource ling =
LingualResource.getInstance("en" , "US");
String usDollar = ling.getLocaleString("USD");
System.out.println("USD=" + usDollar);
LingualResource lingZh =
LingualResource.getInstance("zh" , "CH");
String usDollarZh = lingZh.getLocaleString("USD");
System.out.println("USD=" + usDollarZh);
}
}
代码清单6、客户端类LingualResourceTester的源代码。
假如用户是美国用户,那么在JSP网页中可以通过挪用getLocaleString()要领获得相应的英文说明。好比:
LingualResource ling = LingualResource.getInstance("en" , "US");
String usDollar = ling.getLocaleString("USD");
就会返还
US Dollar
相应地,假如用户是中国大陆的用户,那么在JSP网页中可以通过挪用getLocaleString()要领获得相应的中文说明。好比,
LingualResource ling = LingualResource.getInstance("zh" , "CH");
String usDollar = ling.getLocaleString("USD");
就会返还
美元
Resource文件的内容
为美国英文筹备的Resource文件res_en_US.properties的内容如下:
USD=US Dollar
JPY=Japanese Yen
代码清单7、Resource文件res_en_US.properties的内容。
为简体中文筹备的Resource文件res_zh_CH.properties的内容如下:
USD=美元
JPY=日元
代码清单8、Resource文件res_zh_CH.properties的内容。
问答题
第一题、请问为什么Internationalization又简称作i18n?
第二题、请给出一个按照语言代码和地域代码将数目字名目化的例子。
第三题、请给出一个按照语言代码和地域代码将钱币数目字名目化的例子。
第四题、请给出一个按照语言代码和地域代码将百分比名目化的例子。
问答题谜底
第一题谜底、在英文字Internationalization中,第一个字母i和最后一个字母n之间有18个字母,因此Internationalization又简称作i18n。
第二题谜底、Java库java.text.NumberFormat类提供了对数目字名目标支持,下面给出的就是解答的类图:
图8、对数目字名目支持的解答。
措施的源代码如下:
#p#分页标题#e#
package com.javapatterns.multilingual.number;
import java.util.Locale;
import java.text.NumberFormat;
public class NumberFormatTester
{
static public void displayNumber(
Double amount, Locale currentLocale)
{
NumberFormat formatter;
String amountOut;
formatter =
NumberFormat.getNumberInstance(currentLocale);
amountOut = formatter.format(amount);
System.out.println(amountOut + " "
+ currentLocale.toString());
}
static public void main(String[] args)
{
displayNumber(new Double(1234567.89),
new Locale("en", "US"));
displayNumber(new Double(1234567.89),
new Locale("de", "DE"));
displayNumber(new Double(1234567.89),
new Locale("fr", "FR"));
}
}
代码清单9、Resource文件res_zh_CH.properties的内容。
在运行时,措施回打印出下面的功效:
456,789% en_US
456.789% de_DE
456 789% fr_FR
代码清单10、Resource文件res_zh_CH.properties的内容。
第三题谜底、Java库java.text.NumberFormat类提供了对钱币数目名目标支持。下面给出的就是解答的类图:
图9、对钱币数目名目支持的解答。
措施的源代码如下:
package com.javapatterns.multilingual.number;
import java.util.Locale;
import java.text.NumberFormat;
public class CurrencyFormatTester
{
static public void displayCurrency(Double amount,
Locale currentLocale)
{
NumberFormat formatter;
String amountOut;
formatter =
NumberFormat.getCurrencyInstance(currentLocale);
amountOut = formatter.format(amount);
System.out.println(amountOut + " "
+ currentLocale.toString());
}
static public void main(String[] args)
{
displayCurrency(new Double(1234567.89),
new Locale("en", "US"));
displayCurrency(new Double(1234567.89),
new Locale("de", "DE"));
displayCurrency(new Double(1234567.89),
new Locale("fr", "FR"));
}
}
代码清单11、Resource文件res_zh_CH.properties的内容。
在运行时,措施回打印出下面的功效:
$1,234,567.89 en_US
1.234.567,89 DM de_DE
1 234 567,89 F fr_FR
代码清单12、Resource文件res_zh_CH.properties的内容。
第四题谜底、Java库java.text.NumberFormat类提供了对百分比名目标支持,下面给出的就是解答的类图:
图10、对百分比式支持的解答。
措施的源代码如下:
package com.javapatterns.multilingual.number;
import java.util.Locale;
import java.text.NumberFormat;
public class PercentFormatTester
{
static public void displayPercent(
Double amount, Locale currentLocale)
{
NumberFormat formatter;
String amountOut;
formatter =
NumberFormat.getPercentInstance(currentLocale);
amountOut = formatter.format(amount);
System.out.println(amountOut + " "
+ currentLocale.toString());
}
static public void main(String[] args)
{
displayPercent(new Double(4567.89),
new Locale("en", "US"));
displayPercent(new Double(4567.89),
new Locale("de", "DE"));
displayPercent(new Double(4567.89),
new Locale("fr", "FR"));
}
}
代码清单13、Resource文件res_zh_CH.properties的内容。
在运行时,措施回打印出下面的功效:
1,234,567.89 en_US
1.234.567,89 de_DE
1 234 567,89 fr_FR
代码清单14、Resource文件res_zh_CH.properties的内容。
(本章问答题第二、三、四题的解答参考了[GREEN]的相关例子,在这里我作了一些窜改。)