一、概述
组合模式:
将对象组合成树形结构来表示“整体-部分”的层次结构
组合模式其实很简单,就类似于我们经常在SQL中简单的自连接一样,还有数据结构中的链表、二叉树,其实都可以用组合模式来解释,个人感觉官方的定义甚至还显得更加晦涩难懂了。
而且通常我们使用中并不会在意是不是叶子结点,通俗的说就是一个对象既可以代表根节点又可以代表叶子结点。
二、角色职责与UML
2.1 角色与职责
Component
- 为组合中的对象声明接口
- 在适当的情况下,实现所有类共有接口的缺省行为
- 声明一个接口用于访问和管理Component的子组件
Leaf
- 在组合中表示叶节点对象、叶节点没有子节点
Composite
- 定义有子部件的那些部位的行为
- 存储子部件
通常使用中并不会真的去分Leaf和Composite
2.2 UML图
三、示例
组合模式应用很广泛,为了通俗易懂,就以菜单来举例吧。
而且和上文中说的一样,个人觉得并不需要彻底的分清Leaf还是Composite,因为在用户的调用层面,他接触都是Compoent。
/**
* 菜单抽象类,定义菜单的行为
* @author ZhongJing </p>
*/
public abstract class ComponentMenu {
/**
* 添加一个子菜单
*/
public abstract void addChildMenu(ComponentMenu menu);
/**
* 打印所有子菜单
*/
public abstract void printMenu();
}
/**
* 叶子菜单:没有子菜单的菜单
* @author ZhongJing </p>
*/
public class LeafMenu extends ComponentMenu {
private String menuName;
@Override
public void addChildMenu(ComponentMenu menu) {
System.out.println("错误:该结点为叶子菜单,无法添加子菜单");
}
@Override
public void printMenu() {
System.out.println(menuName);
}
}
/**
* 有子菜单的结点
* @author ZhongJing </p>
*/
public class CompositeMenu extends ComponentMenu {
private String menuName;
private List<ComponentMenu> menuList;
public CompositeMenu(String menuName) {
this.menuName = menuName;
menuList = new ArrayList<>();
}
@Override
public void addChildMenu(ComponentMenu menu) {
menuList.add(menu);
}
@Override
public void printMenu() {
System.out.println(menuName);
if (menuList.size() > 0) {
for (ComponentMenu childMenu : menuList) {
childMenu.printMenu();
}
}
}
}
创建一个测试类进行测试:
public class MainTest {
public static void main(String[] args) {
// 创建一个非叶子菜单作为根菜单
ComponentMenu rootMenu = new CompositeMenu("根菜单");
// 创建一个叶子菜单作为一级菜单:叶子菜单不能添加子菜单
ComponentMenu leafMenu1 = new LeafMenu("一级菜单 ---- 1");
// 创建一个非叶子菜单作为一级菜单
ComponentMenu compositeMenu1 = new CompositeMenu("一级菜单 ---- 2");
// 创建根菜单与一级菜单的联系:添加两个一级菜单到根菜单
rootMenu.addChildMenu(leafMenu1);
rootMenu.addChildMenu(compositeMenu1);
// 给叶子菜单添加一个菜单(不支持,会报错)
leafMenu1.addChildMenu(new CompositeMenu("二级菜单 ---- 1"));
// 给非叶子菜单添加一个菜单
compositeMenu1.addChildMenu(new CompositeMenu("二级菜单 ---- 2"));
// 按层次打印遍历所有菜单
rootMenu.printMenu();
}
}
运行结果如下:
错误:该结点为叶子菜单,无法添加子菜单
根菜单
一级菜单 ---- 1
一级菜单 ---- 2
二级菜单 ---- 2
通过结果可以看到,我们对叶子菜单进行的限制确实起到了作用,虽然调用时都是以ComponentMenu对象来调用的,但是叶子菜单确实无法添加子菜单。
其实这种结构的组合并不是那么的常用,如果没有叶子的限制,我一般使用的下面这种结构:
同样以菜单为例
/**
* 菜单,既可以作为叶子也可以作为根
*/
public class Menu{
private String menuName;
private List<Menu> menuList;
public Menu(String menuName) {
this.menuName = menuName;
menuList = new ArrayList<>();
}
/**
* 添加一个子菜单
*/
public void addChildMenu(Menu menu) {
menuList.add(menu);
}
/**
* 打印所有子菜单
*/
public void printMenu() {
System.out.println(menuName);
for (Menu menu : menuList) {
menu.printMenu();
}
}
}
测试一下:
/**
* @author ZhongJing </p>
*/
public class MainTest2 {
public static void main(String[] args) {
Menu rootMenu = new Menu("根菜单");
Menu oneMenu1 = new Menu("一级菜单1");
Menu oneMenu2 = new Menu("一级菜单2");
oneMenu1.addChildMenu(new Menu("二级菜单1"));
oneMenu1.addChildMenu(new Menu("二级菜单2"));
oneMenu2.addChildMenu(new Menu("二级菜单3"));
rootMenu.addChildMenu(oneMenu1);
rootMenu.addChildMenu(oneMenu2);
rootMenu.printMenu();
}
}
运行结果如下:
根菜单
一级菜单1
二级菜单1
二级菜单2
一级菜单2
二级菜单3