下面的代码来自我们某一平台产品前端源码(Java语言)中:
private static Map<String, Map<String, String>> constructConstrainMap() {
Map<String, Map<String, String>> typeConstrainMap = new HashMap<String, Map<String, String>>();
Map<String, String> imageConstrainPatternMap = new HashMap<String, String>();
imageConstrainPatternMap.put("allowdPatten", "^[a-zA-Z0-9_~.=@-]$");
imageConstrainPatternMap.put("allowdMin", "1");
imageConstrainPatternMap.put("allowdMax", "256");
imageConstrainPatternMap.put("allowdValue", null);
imageConstrainPatternMap.put("noEcho", "false");
imageConstrainPatternMap.put("description", "com.huawei.....");
typeConstrainMap.put(TPropType.IAA_S_IMAGE_ID.value(), imageConstrainPatternMap)
Map<String, String> netWorkConstrainPatternMap = new HashMap<String, String>();
// 省略 put ...
Map<String, String> containerConstrainPatternMap = new HashMap<String, String>();
// 省略 put ...
// 省略 其它的Constrain代码 ...
}
上面的代码在一个方法中构造了16个Constrain,它是提供给BME控件用于输入框的校验。显然代码出现了重复(相似),也较容易想到采用外部配置文件方式来简化样板代码,但采用什么配置方式呢?
无论哪种配置文件格式,解释库几乎都提供配置内容直接到Java对象的映射。比如XML,JDK1.6起提供JAXB(Java Architecture for XML Binding)来序列化与反序列化XML文件。不过由于XML技术过于古老,JDK11把JAXB从标准模块移除了,需要额外引入依赖或使用Jackson的JAXB能力。倘若使用DOM或SAX来解释XML,要写的代码有些多,也容易出错,直观感觉还不如上面一行一行代码写死配置内容来得快。而采用JAXB,定义映射结构类加上解释代码,也就20行左右代码可以搞定。那我们来尝试优化一下此案例代码:
第一步,定义XML的格式:
<Constrains>
<Constrain id="image" allowdPatten="^[a-zA-Z0-9_~.=@-]$" allowdMin="1" allowdMax="256" noEcho="false" description="com...">
<Constrain id="network" allowdPatten="^[a-zA-Z0-9_]$" allowdMin="1" allowdMax="256" noEcho="false" allowdValue="local/external" description="com...">
<!--省略其它的-->
</Constrains>
第二步,定义Java类结构:
@XmlAccessorType(XmlAccessType.FIELD)
@Getter
@Setter
public class Constrain {
@XmlAttribute(name="id")
private String id;
@XmlAttribute(name="allowdPatten")
private String allowdPatten;
@XmlAttribute(name="allowdMin")
private int allowdMin;
@XmlAttribute(name="iallowdMaxd")
private int allowdMax;
@XmlAttribute(name="noEcho")
private boolean noEcho;
@XmlAttribute(name="allowdValue")
private String allowdValue;
@XmlAttribute(name="description")
private String description;
}
@XmlRootElement(name="Constrains")
@Getter
@Setter
public class Constrains {
@XmlElement(name = "Constrain")
protected List<Constrain> items;
}
第三步,解释配置文件
try (InputStream is = new FileInputStream("constrains.xml")) {
JAXBContext jc = JAXBContext.newInstance(Constrains.class);
Unmarshaller unmarshaller = jc.createUnmarshaller();
Constrains constrains = (Constrains)unmarshaller.unmarshal(is)
}
不过上面的代码有两个坑:
再优化一下:
// JAXBContext jc = JAXBContext.newInstance(Constrains.class); 在初始化或静态区中确保jc只new一次
XMLInputFactory xif = XMLInputFactory.newFactory();
xif.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false); // 关闭外部实体解释支持
xif.setProperty(XMLInputFactory.SUPPORT_DTD, false); // 注:视情况true/false,当存在DTD,可以由DTD检查XML合法性,请参考要相关文档
try (XMLStreamReader xsr = xif.createXMLStreamReader(new FilterInputStream("constrains.xml"), "UTF-8")) {
Unmarshaller unmarshaller = jc.createUnmarshaller();
Constrains constrains = (Constrains)unmarshaller.unmarshal(xsr)
}
读取配置文件是一个软件系统必不可少的一部分,现代编程语言通常内置不同格式的解释库。面向对象语言,也通常支持直接从配置内容映射到对象树,使用起来则非常的简洁方便。
配置文件不仅是给软件程序读取,也需要给维护者阅读修改,或自动化工具修改(如部署安装),则可以从如下几个方面考虑:
常用有如下几种配置格式:
网上有人总结如下:
配置文件的选择还是需要考虑实际使用场景,个人没有倾向性。
用户界面中对输入数据的约束可以粗略的分为两种:
在一个系统中,我们总是会遇到一些参数,它们和具体的程序逻辑无关,比如数据库的地址,启动时绑定的IP与端口。显然这些参数更不适合被“硬编码”在代码中。
通常需要把这类的数据抽出来放在配置文件,方便扩展与修改。广义上讲,配置文件也是属于代码一种承载形式。通过配置文件来存放数据,其实把纯数据从主体代码中移到配置文件中,本质是实现配置数据与逻辑代码的分离。
回到案例中的代码,显然也是对用户输入数据的约束规则,可能这种约束就是固定不变的,从是否可变的角色来看,把它“硬编码”到代码在代码似乎问题不大。
但当我们将这类配置数据从代码中分离出来,则可以:
软件系统中必不可少地会出现配置类数据,数据是否“硬编码”到代码中,还是从代码中分离出来,分离时采用什么的配置格式,都要视场景来选择不同的策略。配置类数据代码通常也是重复相似代码问题的高发之地,拒绝写重复代码,让代码职责清晰,降低出错,把配置数据从代码中分离是不错的方向。