Java - GoF设计模式详解8(组合模式)
八、组合模式
1,基本介绍
(1)组合模式(Composite)又叫部分整体模式,该模式将对象组合成树形结构以表示“整体-部分”的层次结构,使用户对单个对象和组合对象的使用具有一致性。
(2)该模式中包含的角色及其职责如下:
- Component(抽象构件):它可以是接口或抽象类,为叶子构件和容器构件对象声明接口,在该角色中可以包含所有子类共有行为的声明和实现。在抽象构件中定义了访问及管理它的子构件的方法,如增加子构件、删除子构件、获取子构件等。
- Leaf(叶子构件):它在组合结构中表示叶子节点对象,叶子节点没有子节点,它实现了在抽象构件中定义的行为。对于那些访问及管理子构件的方法,可以通过异常等方式进行处理。
- Composite(容器构件):它在组合结构中表示容器节点对象,容器节点包含子节点,其子节点可以是叶子节点,也可以是容器节点,它提供一个集合用于存储子节点,实现了在抽象构件中定义的行为,包括那些访问及管理子构件的方法,在其业务方法中可以递归调用其子节点的业务方法。
2,样例代码
(1)这里我们使用组合模式来实现一个用于表示文件和文件夹的文件系统。首先定义一个抽象构件 FileSystemComponent,声明了所有文件和文件夹子类共有的方法。// 抽象构件 public interface FileSystemComponent { void ls(); // 显示该构件内容 void add(FileSystemComponent component); // 添加一个子构件 void remove(FileSystemComponent component); // 移除一个子构件 }
(2)接着定义一个 Folder 类来表示文件夹,我们可以实现 ls() 方法以打印文件夹的名称,然后递归地调用子构件的 ls() 方法打印子构件的信息:
// 文件夹类(容器构件) public class Folder implements FileSystemComponent { private String name; private List<FileSystemComponent> children; public Folder(String name) { this.name = name; this.children = new ArrayList<>(); } public void ls() { System.out.println(name); for (FileSystemComponent child : children) { child.ls(); } } public void add(FileSystemComponent component) { children.add(component); } public void remove(FileSystemComponent component) { children.remove(component); } }
(3)然后定义一个 File 类表示文件,我们可以实现 ls() 方法直接打印该文件的名称和大小。同时由于文件节点无添加和移除子节点功能,则重写这两个方法时抛出异常:
// 文件类(叶子构件) public class File implements FileSystemComponent { private String name; private int size; public File(String name, int size) { this.name = name; this.size = size; } public void ls() { System.out.println(name + " (" + size + " bytes)"); } public void add(FileSystemComponent component) { throw new UnsupportedOperationException(); } public void remove(FileSystemComponent component) { throw new UnsupportedOperationException(); } }
(4)最后测试一下:
public class Test { public static void main(String[] args) { FileSystemComponent file1 = new File("hangge.txt", 10); FileSystemComponent file2 = new File("image.jpg", 230); FileSystemComponent file3 = new File("index.html", 55); FileSystemComponent folder1 = new Folder("info"); FileSystemComponent folder2 = new Folder("web"); FileSystemComponent folder3 = new Folder("root"); folder1.add(file1); folder1.add(file2); folder2.add(file3); folder3.add(folder1); folder3.add(folder2); System.out.println("----- 显示某个文件信息 ------"); file2.ls(); System.out.println("----- 显示某个文件夹信息 ------"); folder3.ls(); } }
附一:JDK 中的组合模式
(1)Java 的集合类 HashMap 就使用了组合模式,具体结构如下:
- Map 就是一个抽象构件(Component)
- HashMap 是一个容器构件(Composite),只不过在 HashMap 与 Component 之间多了一层 AbstractMap<K,V>,并且重写了 Map 中 put()、putAll() 等方法;
- Node 是 HashMap 的静态内部类,类似叶子构件(Leaf),这里就没有 put、putAll
(2)下面是一个简单的使用样例,其中 putall() 方法参数是 map 集合,其功能就是将这个集合存放到另一个集合中的下面。
public class Test { public static void main(String[] args) { Map<Object, Object> map1 = new HashMap<>(); map1.put(0, "zero"); System.out.println(map1); Map<Object, Object> map2 = new HashMap<Object, Object>(); map2.put(1, "one"); map2.put(2, "two"); map2.put(3, "three"); map1.putAll(map2); System.out.println(map1); } }
附二:Mybatis 中的组合模式
(1)MyBatis 的强大特性之一便是它的动态 SQL,其通过 if, choose, when, otherwise, trim, where, set, foreach 标签,可组合成非常灵活的 SQL 语句,从而提高开发人员的效率。
<select id="findActiveBlogLike" resultType="Blog"> SELECT * FROM BLOG <where> <if test="state != null"> state = #{state} </if> <if test="title != null"> AND title like #{title} </if> <if test="author != null and author.name != null"> AND author_name like #{author.name} </if> </where> </select>
(2)Mybatis 在处理动态 SQL 节点时,应用到了组合设计模式,MyBatis 会将映射文件中定义的静态 SQL 节点、文本节点等解析成对应的 SqlNode 实现,形成树形结构。下方是 SqlNode 的继承关系图:
(3)其中,SqlNode 就是扮演组合模式中的 Component 角色,Sql 标签会解析成 SqlNode 对象。
public interface SqlNode { boolean apply(DynamicContext context); }
(4)而 MixedSqlNode 类扮演组合模式的 Composite 角色,它有个 SqlNode 的集合类,记录 SqlNode 对象,apply 方法就是遍历集合,依次调用自己的 apply() 方法:
public class MixedSqlNode implements SqlNode { private final List<SqlNode> contents; public MixedSqlNode(List<SqlNode> contents) { this.contents = contents; } @Override public boolean apply(DynamicContext context) { contents.forEach(node -> node.apply(context)); return true; } }
(5)剩余其他 SqlNode 的实现类就充当组合模式的 Left 角色了:
- TextSqlNode 是包含 ${} 的动态 sql 片段
- TrimSqlNode 是解析出的 trim 标签的对象,trim 标签可以去除 sql 的 and、逗号或者拼接 where 关键字等
- IfSqlNode 是解析出 if 标签、when 标签的类
- StaticTextSqlNode 是非动态的 sql 片段
(6)下面是 TextSqlNode 的 apply() 方法的实现,首先创建 GenericTokenParser 解析器,然后解析包含 ${} 的 sql 片段,解析后保存到 DynamicContext 中。
@Override public boolean apply(DynamicContext context) { GenericTokenParser parser = createParser(new BindingTokenParser(context, injectionFilter)); context.appendSql(parser.parse(text)); return true; } private GenericTokenParser createParser(TokenHandler handler) { return new GenericTokenParser("${", "}", handler); }