定义:
是一种将对象组合成树状的层结构的模式,用来表示“部分-整体”的关系,使用户对象单个对象和组合对象具有一致的访问性。
主要角色:
抽象构件(抽象角色):为树叶构建和树枝构建声明公共接口,并实现它们的默认行为。
树叶构件(叶子组件):叶子节点,没有子节点。相当于一个文件,不能再分。
树枝构件(容器组件):分支节点,有子节点。相当于一个文件夹,里面包可能含有文件夹和文件。并有对子文件的操作方法,如删除文件,增加文件等。
优点:
组合模式使得客户端可以一致地处理单个对象和组合对象,无须关系自己处理的是单个对象还是组合对象;简化了客户端代码。
更容易在组合体内加入新的对象,不会更改源代码,符合开闭原则
定义了包含叶子对象和容器对象的类层次结构,叶子对象可以被组合成更复杂的容器对象,而这个容器对象又可以被组合,这样不断递归下去,可以形成复杂的树形结构。
缺点:
客户端需要花更多时间理清类之间的层次关系。
不容易限制容器中的构件。
不容易用继承的方法来增加构件的新功能。
使用场景:
系统对象层次具备整体和部分,呈树形结构,且要求具备统一行为(如树形菜单,操作系统目录结构,公司组织架构等)。
希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象时。
实例
【例】软件菜单
如下图,我们在访问别的一些管理系统时,经常可以看到类似的菜单。一个菜单可以包含菜单项(菜单项是指不再包含其他内容的菜单条目),也可以包含带有其他菜单项的菜单,因此使用组合模式描述菜单就很恰当,我们的需求是针对一个菜单,打印出其包含的所有菜单以及菜单项的名称。
类图
菜单组件抽象类(抽象根节点)
/**
* @author pzz
* @date 2022/5/17
* 菜单组件
* 抽象根节点
*/
public abstract class MenuComponent {
//菜单组件的名称
protected String name;
//菜单组件的层级
protected int level;
//添加子菜单
public void add(MenuComponent menuComponent) {
throw new UnsupportedOperationException();
}
//移除子菜单
public void remove(MenuComponent menuComponent) {
throw new UnsupportedOperationException();
}
//获取指定的子菜单
public MenuComponent getChild(int index) {
throw new UnsupportedOperationException();
}
//获取菜单或者菜单项的名称
public String getName() {
return name;
}
//打印菜单名称的方法(包含子菜单和字菜单项)
public abstract void print();
}
这里的MenuComponent定义为抽象类,因为有一些共有的属性和行为要在该类中实现,Menu和MenuItem类就可以只覆盖自己感兴趣的方法,而不用搭理不需要或者不感兴趣的方法,
举例来说,Menu类可以包含子菜单,因此需要覆盖add()、remove()、getChild()方法,但是MenuItem就不应该有这些方法。
这里给出的默认实现是抛出异常,你也可以根据自己的需要改写默认实现。
菜单类(树枝节点)
/**
* @author pzz
* @date 2022/5/17
* 菜单类
* 树枝节点
*/
public class Menu extends MenuComponent{
//菜单可以有多个子菜单和子菜单项
private List<MenuComponent> menuComponentList = new ArrayList<>();
//构造方法
public Menu(String name,int level){
this.name = name;
this.level = level;
}
/**
* 添加子菜单或者子菜单项
* @param menuComponent
*/
@Override
public void add(MenuComponent menuComponent) {
menuComponentList.add(menuComponent);
}
/**
* 删除子菜单或者子菜单项
* @param menuComponent
*/
@Override
public void remove(MenuComponent menuComponent) {
menuComponentList.add(menuComponent);
}
@Override
public MenuComponent getChild(int index) {
return menuComponentList.get(index);
}
@Override
public void print() {
//打印菜单名称
for(int i = 0; i < level; i++) {
System.out.print("--");
}
System.out.println(name);
//打印子菜单或者子菜单项名称
for (MenuComponent component : menuComponentList) {
component.print();
}
}
}
Menu类已经实现了除了getName方法的其他所有方法,因为Menu类具有添加菜单,移除菜单和获取子菜单的功能。
菜单项类(叶子节点)
/**
* @author pzz
* @date 2022/5/17
* 菜单项
* 叶子节点
*/
public class MenuItem extends MenuComponent{
//构造方法
public MenuItem(String name,int level){
this.name = name;
this.level = level;
}
@Override
public void print() {
//打印菜单项的名称
for(int i = 0; i < level; i++) {
System.out.print("--");
}
System.out.println(name);
}
}
MenuItem是菜单项,不能再有子菜单,所以添加菜单,移除菜单和获取子菜单的功能并不能实现。
测试类
/**
* @author pzz
* @date 2022/5/17
* 测试类
*/
public class Client {
public static void main(String[] args) {
//创建菜单树
MenuComponent menu1 = new Menu("菜单管理",2);
menu1.add(new MenuItem("页面访问",3));
menu1.add(new MenuItem("展开菜单",3));
menu1.add(new MenuItem("编辑菜单",3));
menu1.add(new MenuItem("删除菜单",3));
menu1.add(new MenuItem("新增菜单",3));
MenuComponent menu2 = new Menu("权限管理",2);
menu2.add(new MenuItem("页面访问",3));
menu2.add(new MenuItem("提交保存",3));
MenuComponent menu3 = new Menu("角色管理",2);
menu3.add(new MenuItem("页面访问",3));
menu3.add(new MenuItem("新增角色",3));
menu3.add(new MenuItem("修改角色",3));
//创建一级菜单
MenuComponent component = new Menu("系统管理",1);
//将二级菜单添加到一级菜单中
component.add(menu1);
component.add(menu2);
component.add(menu3);
//打印菜单名称(如果有子菜单一块打印)
component.print();
}
}
测试结果
组合模式有:
-
透明组合模式
透明组合模式中,抽象根节点角色中声明了所有用于管理成员对象的方法,比如在示例中
MenuComponent
声明了add
、remove
、getChild
方法,这样做的好处是确保所有的构件类都有相同的接口。透明组合模式也是组合模式的标准形式。透明组合模式的缺点是不够安全,因为叶子对象和容器对象在本质上是有区别的,叶子对象不可能有下一个层次的对象,即不可能包含成员对象,因此为其提供 add()、remove() 等方法是没有意义的,这在编译阶段不会出错,但在运行阶段如果调用这些方法可能会出错(如果没有提供相应的错误处理代码)
-
安全组合模式
在安全组合模式中,在抽象构件角色中没有声明任何用于管理成员对象的方法,而是在树枝节点
Menu
类中声明并实现这些方法。安全组合模式的缺点是不够透明,因为叶子构件和容器构件具有不同的方法,且容器构件中那些用于管理成员对象的方法没有在抽象构件类中定义,因此客户端不能完全针对抽象编程,必须有区别地对待叶子构件和容器构件。
总结:
- 组合模式用于将多个对象组合成树形结构以表示“整体-部分”的结构层次。组合模式对单个对象(叶子对象)和组合对象(容器对象)的使用具有一致性。
- 组合对象的关键在于它定义了一个抽象构建类,它既可表示叶子对象,也可表示容器对象,客户仅仅需要针对这个抽象构建进行编程,无须知道他是叶子对象还是容器对象,都是一致对待。
- 组合模式虽然能够非常好地处理层次结构,也使得客户端程序变得简单,但是它也使得设计变得更加抽象,而且也很难对容器中的构件类型进行限制,这会导致在增加新的构件时会产生一些问题。
结束!!
此时,年轻已经不能作为失败的借口了。