介绍过案例脚本模型之后,按照惯例,接下来要展示的是语义模型。由于案例本身复杂度较低,其涉及的语义模型亦较为简单。若需设计如Java Bean Validation般功能完备的验证框架,则需考量更多内容。为聚焦本章语法分析主题,本案例仅针对数字类型信息的验证展开讨论。实际场景中,字符串、数组及自定义类型的验证实现,以及正则表达式、自定义验证逻辑等模式的支持,均可参考本案例的设计思路。

        对于代码7-4所示案例,核心要素在于各类运算规则的实现,主要分为关系运算和逻辑运算两类:前者涵盖大于、等于、小于等比较型操作;后者主要包括逻辑与、逻辑或、逻辑非等操作。若读者对Java、C等编程语言有一定了解,理解这些概念将不存在障碍。显然,将具体运算逻辑封装于语义模型中是合理的设计选择,但这会导致语义模型数量显著增加——需为每类关系运算和逻辑运算分别设计对应的模型。若将所有语义模型完整罗列,不仅无法有效说明问题,还可能影响读者的阅读体验。因此,为简化阐述,笔者仅对部分领域对象进行建模,建模结果如图 7.1所示。

图 7.1 参数验证示例DSL语义模型

        关系运算Equal(等于)和LessThan(小于)继承自抽象类CompareExpression。前两个都是比较运算的具体类,实现了对应的比较逻辑;后者则是比较运算的基类,包含了两个Compareable类型的对象source和target以支撑比较逻辑的实现,读者可将它们理解为关系运算中的左元与右元。逻辑运算And(与)和Or(或)继承自抽象类Logical,同比较运算类似,前两者是具体类;后者为抽象类,包含了两个Validatable类型的字段x和y,表示逻辑运算的左元与右元。

        类型CompareExpression和Logical均都实现了接口Validatable。后者包含了名为validate()的方法,用于触发验证逻辑。不过上述两个抽象类并未实现该方法,而是由具体类如Equal、And等负责实现。

        总体来看,语义模型的结构比较简单,唯一要注意的是Validatable和Logical这两个类型之间的关系,具体实现方式有点类似于装饰设计模式(Decorator Pattern),这也导致了表面上看起来很简单的语义模型,其运行时结构却非常复杂。具体来说,Logical类型不仅实现了Validatable,同时也包含了两个该类型的对象,即x、y。通过图 7.1还可知,关系运算和逻辑运算都实现了Validatable接口,这就意味着x、y的具体类型可能是LessThan、Euqal等关系运算,也可能是And、Or等逻辑运算。以代码7-4所示DSL为例,其对应的语义模型运行时结构将呈图 7.2所示(注:GE和LE分别表示大于等于、小于等于两个关系运算,这两个类型并未出现在图 7.1中)的样子。

图 7.2 代码7-4所示DSL的运行时语义模型结构

        可见,DSL表达式的复杂度与语义模型的运行时结构复杂度呈正相关。对象间可能出现多层嵌套,导致理解难度显著增加。因此,建议读者在实践时优先从简单案例入手,例如不含逻辑运算的单条比较运算,在充分掌握其原理后再逐步提升复杂度。部分读者可能存在疑问:为何类间静态关系较为简单,而运行时对象结构却如此复杂?这一现象由多种因素导致,多态机制、组合模式以及设计模式的应用均可能引发此类情况。这一特性既是OOP的优势——可通过少量类表达复杂业务逻辑;同时也构成挑战——运行时对象结构的复杂性增加了程序理解与调试的难度,要求开发者必须深入理解OOP的固有特性,才能充分发挥该编程模式的优势。

        在了解语义模型的基本结构后,接下来进入代码展示环节。笔者将语义模型相关代码置于名为“model”的包中,以实现代码的便捷管理。在实践中,建议读者依据代码功能进行分组管理。当DSL规模较大时,涉及的代码量通常较多,这种管理方式可使代码管理工作更具条理性。

        书归正文。首先展示的是比较运算的语义模型。简单起见,笔者只展示小于运算符LessThan及其父类CompareExpression的代码,具体如代码7-5所示:

代码7-5

abstract class CompareExpression implements Validatable {
    public Comparable source;
    public Comparable target;
}

class LessThan extends CompareExpression {
    @Override
    public boolean validate() {
        if (source != null && target != null) {
            return source.compareTo(target) < 0;
        }
        return false;
    }
}

        类型LessThan中的validate()方法实现了两个对象的比较逻辑,内容比较简单,笔者不做过多的说明。如果需要加入其他比较类型的语义模型的话,只需新增加一个类型并参考代码7-5所示的方式实现具体的比较逻辑即可。

        接下来要展示的是逻辑运算语义模型。同比较运算,我们也只展示逻辑与And及其父类Logical的代码,片段如代码7-6所示:

代码7-6

abstract class Logical implements Validatable {
    Validatable x;
    Validatable y;
}

class And extends Logical {
    public And(Validatable x, Validatable y) {
        super(x, y);
    }

    @Override
    public boolean validate() {
        if (x == null && y == null) {
            return true;
        }
        if (x == null || y == null) {
            return false;
        }
        return x.validate() && y.validate();
    }
}

        至此,本章案例涉及的语义模型及其对应代码已讲解和展示完毕。尽管前文已对语义模型的概念进行过阐述,但通过案例实现知识的归纳总结仍不失为有效方式。因此,在结束本章之前,就语义模型的正确使用作如下补充:  

  1. 语义模型可用于表达领域概念,例如前文案例中的LessThan或And。  
  2. 充血语义模型的设计需遵循面向对象设计原则,如开闭原则、依赖倒置原则等。同时,合理运用设计模式可增强语义模型的表现力与扩展能力。

上一章  下一章

Logo

助力广东及东莞地区开发者,代码托管、在线学习与竞赛、技术交流与分享、资源共享、职业发展,成为松山湖开发者首选的工作与学习平台

更多推荐