下面的代码来自我们某一平台产品前端源码(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与端口。显然这些参数更不适合被“硬编码”在代码中。
通常需要把这类的数据抽出来放在配置文件,方便扩展与修改。广义上讲,配置文件也是属于代码一种承载形式。通过配置文件来存放数据,其实把纯数据从主体代码中移到配置文件中,本质是实现配置数据与逻辑代码的分离。
回到案例中的代码,显然也是对用户输入数据的约束规则,可能这种约束就是固定不变的,从是否可变的角色来看,把它“硬编码”到代码在代码似乎问题不大。
但当我们将这类配置数据从代码中分离出来,则可以:
软件系统中必不可少地会出现配置类数据,数据是否“硬编码”到代码中,还是从代码中分离出来,分离时采用什么的配置格式,都要视场景来选择不同的策略。配置类数据代码通常也是重复相似代码问题的高发之地,拒绝写重复代码,让代码职责清晰,降低出错,把配置数据从代码中分离是不错的方向。