组合设计模式UML类图
组合设计模式java代码实现
/** |
应用场景
java.awt中的组合模式
Java GUI分两种:
AWT(Abstract Window Toolkit):抽象窗口工具集,是第一代的Java GUI组件。绘制依赖于底层的操作系统。基本的AWT库处理用户界面元素的方法是把这些元素的创建和行为委托给每个目标平台上(Windows、 Unix、 Macintosh等)的本地GUI工具进行处理。
Swing,不依赖于底层细节,是轻量级的组件。现在多是基于Swing来开发。
我们来看一个AWT的简单示例:
import java.awt.*; |
运行后窗体显示如下
我们在Frame容器中添加了三个不同的构件 Button、Label、TextField,还添加了一个 Panel 容器,Panel 容器中又添加了 Button、Label、TextField 三个构件,为什么容器 Frame 和 Panel 可以添加类型不同的构件和容器呢?
我们先来看下AWT Component的类图
GUI组件根据作用可以分为两种:基本组件和容器组件。
基本组件又称构件,诸如按钮、文本框之类的图形界面元素。
容器是一种比较特殊的组件,可以容纳其他组件,容器如窗口、对话框等。所有的容器类都是 java.awt.Container 的直接或间接子类
容器父类 Container 的部分代码如下
public class Container extends Component { |
容器父类 Container 内部定义了一个集合用于存储 Component 对象,而容器组件 Container 和 基本组件如 Button、Label、TextField 等都是 Component 的子类,所以可以很清楚的看到这里应用了组合模式
Component 类中封装了组件通用的方法和属性,如图形的组件对象、大小、显示位置、前景色和背景色、边界、可见性等,因此许多组件类也就继承了 Component 类的成员方法和成员变量,相应的成员方法包括:
getComponentAt(int x, int y) |
Java集合中的组合模式
HashMap 提供 putAll 的方法,可以将另一个 Map 对象放入自己的存储空间中,如果有相同的 key 值则会覆盖之前的 key 值所对应的 value 值
public class Test { |
输出结果
map1: {aa=1, bb=2, cc=3} |
查看 putAll 源码
putAll 接收的参数为父类 Map 类型,所以 HashMap 是一个容器类,Map 的子类为叶子类,当然如果 Map 的其他子类也实现了 putAll 方法,那么它们都既是容器类,又都是叶子类
同理,ArrayList 中的 addAll(Collection<? extends E> c) 方法也是一个组合模式的应用,在此不做探讨
Mybatis SqlNode中的组合模式
MyBatis 的强大特性之一便是它的动态SQL,其通过 if, choose, when, otherwise, trim, where, set, foreach 标签,可组合成非常灵活的SQL语句,从而提高开发人员的效率。
来几个官方示例:
动态SQL – IF
<select id="findActiveBlogLike" resultType="Blog"> |
动态SQL – choose, when, otherwise
<select id="findActiveBlogLike" resultType="Blog"> |
动态SQL – where
<select id="findActiveBlogLike" resultType="Blog"> |
动态SQL – foreach
<select id="selectPostIn" resultType="domain.blog.Post"> |
Mybatis在处理动态SQL节点时,应用到了组合设计模式,Mybatis会将映射配置文件中定义的动态SQL节点、文本节点等解析成对应的 SqlNode 实现,并形成树形结构。
SQLNode 的类图如下所示
需要先了解 DynamicContext 类的作用:主要用于记录解析动态SQL语句之后产生的SQL语句片段,可以认为它是一个用于记录动态SQL语句解析结果的容器
抽象构件为 SqlNode 接口,源码如下
public interface SqlNode { |
apply 是 SQLNode 接口中定义的唯一方法,该方法会根据用户传入的实参,参数解析该SQLNode所记录的动态SQL节点,并调用 DynamicContext.appendSql() 方法将解析后的SQL片段追加到 DynamicContext.sqlBuilder 中保存,当SQL节点下所有的 SqlNode 完成解析后,我们就可以从 DynamicContext 中获取一条动态生产的、完整的SQL语句
然后来看 MixedSqlNode 类的源码
public class MixedSqlNode implements SqlNode { |
MixedSqlNode 维护了一个 List
对于其他SqlNode子类的功能,稍微概括如下:
- TextSqlNode:表示包含 ${} 占位符的动态SQL节点,其 apply 方法会使用 GenericTokenParser 解析 ${} 占位符,并直接替换成用户给定的实际参数值
- IfSqlNode:对应的是动态SQL节点
节点,其 apply 方法首先通过 ExpressionEvaluator.evaluateBoolean() 方法检测其 test 表达式是否为 true,然后根据 test 表达式的结果,决定是否执行其子节点的 apply() 方法 - TrimSqlNode :会根据子节点的解析结果,添加或删除相应的前缀或后缀。
- WhereSqlNode 和 SetSqlNode 都继承了 TrimSqlNode
- ForeachSqlNode:对应
标签,对集合进行迭代 - 动态SQL中的
、 、 分别解析成 ChooseSqlNode、IfSqlNode、MixedSqlNode
综上,SqlNode 接口有多个实现类,每个实现类对应一个动态SQL节点,其中 SqlNode 扮演抽象构件角色,MixedSqlNode 扮演容器构件角色,其它一般是叶子构件角色