一、模式介绍
1.1、定义
也叫整体-部分(Part-Whole)模式,将一组对象组织成树形结构,以表示一种整体-部分的层次结构。组合让客户端可以统一单个对象和组合对象的处理逻辑。客户端代指代码的使用者。
组合模式一般用来描述整体与部分的关系,它将对象组织到树形结构中,顶层节点被称为根节点,根节点下面可以包含树枝节点和叶子节点,树枝节点下面又可以包含树枝节点和叶子节点。
从图中可以看出,根节点和树枝节点本质上属于同一种数据类型,可以作为容器使用;而叶子节点与树枝节点在语义上不属于同一种类型。但是在组合模式中,会把树枝节点和叶子节点看作同一种数据类型(用统一接口定义),让它们具备一致行为。
这样,在组合模式中,整个树形结构中的对象都属于同一种类型,带来的好处就是用户不需要辨别是树枝节点还是叶子节点,可以直接进行操作,给用户带来极大的便利。
1.2、优点
- 组合模式使得客户端代码可以一致的处理单个对象和组合对象,无须关心自己处理的是单个对象,还是组合对象,这简化了客户端代码
- 更容易在组合体内加入新的对象,客户端不会因为加入了新的对象而更改源代码,满足开闭原则
1.3、缺点
- 设计较复杂,客户端需要花更多时间理清类之间的层次关系
- 不容易限制容器中的构件
- 不容易用继承的方法来增加构件的新功能
二、结构与实现
2.1、结构
- 抽象构件角色(Component):为树叶构件和树枝构件声明公共接口,并实现它们默认的行为。总的抽象类或接口,定义一些通用的方法,比如新增、删除、更新等
- 树叶构件角色(Leaf):是组合中的叶节点对象,它没有子节点,用于继承或实现抽象构件
- 树枝构件角色(Composite):是组合中的分支节点对象,他有子节点,用于继承和实现抽象构件。它的主要作用是存储和管理子部件,通常包含 add()、remove()、getChild() 等方法
组合模式分为:透明式的组合模式和安全式的组合模式
- 在透明式的组合模式中,抽象构建还声明访问和管理子类的接口,管理工作由树枝构件完成
- 在安全式的组合模式中,抽象构件角色不声明访问和管理子类的接口,管理工作由树枝构件完成。
透明式的组合模式
安全式组合模式
2.2、实现
2.2.1、类图
2.2.2、MenuComponent
package com.erlang.composite;
/**
* @description: 菜单组件
* @author: erlang
* @since: 2022-02-15 21:45
*/
public abstract class MenuComponent {
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() {
throw new UnsupportedOperationException();
}
public String getDescription() {
throw new UnsupportedOperationException();
}
public double getPrice() {
throw new UnsupportedOperationException();
}
public boolean isVegetarian() {
throw new UnsupportedOperationException();
}
public void print() {
throw new UnsupportedOperationException();
}
}
2.2.3、MenuItem
package com.erlang.composite;
/**
* @description: 菜单元素
* @author: erlang
* @since: 2022-02-15 21:45
*/
public class MenuItem extends MenuComponent {
String name;
String description;
boolean vegetarian;
double price;
public MenuItem(String name, String description, boolean vegetarian, double price) {
this.name = name;
this.description = description;
this.vegetarian = vegetarian;
this.price = price;
}
@Override
public String getName() {
return name;
}
@Override
public String getDescription() {
return description;
}
@Override
public double getPrice() {
return price;
}
@Override
public boolean isVegetarian() {
return vegetarian;
}
@Override
public void print() {
System.out.print(" " + getName());
// 是否为素食
if (isVegetarian()) {
System.out.print("(素食)");
}
System.out.println(", " + getPrice());
System.out.println(" -- " + getDescription());
}
}
2.2.4、Menu
package com.erlang.composite;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* @description: 菜单
* @author: erlang
* @since: 2022-02-15 21:46
*/
public class Menu extends MenuComponent {
private List<MenuComponent> children = new ArrayList<>();
private String name;
private String description;
public Menu(String name, String description) {
this.name = name;
this.description = description;
}
@Override
public void add(MenuComponent menuComponent) {
children.add(menuComponent);
}
@Override
public void remove(MenuComponent menuComponent) {
children.remove(menuComponent);
}
@Override
public MenuComponent getChild(int index) {
return children.get(index);
}
@Override
public String getName() {
return name;
}
@Override
public String getDescription() {
return description;
}
@Override
public void print() {
System.out.print(" " + getName());
System.out.println(", " + getDescription());
System.out.println("-----------------------------");
Iterator<MenuComponent> iterator = children.iterator();
while (iterator.hasNext()) {
iterator.next().print();
}
}
}
2.2.5、Waitress
package com.erlang.composite;
/**
* @description: 服务员
* @author: erlang
* @since: 2022-02-15 21:45
*/
public class Waitress {
MenuComponent menus;
public Waitress(MenuComponent menus) {
this.menus = menus;
}
public void printMenu() {
menus.print();
}
}
2.2.6、MenuTestDrive
package com.erlang.composite;
/**
* @description:
* @author: erlang
* @since: 2022-02-15 22:04
*/
public class MenuTestDrive {
public static void main(String[] args) {
MenuComponent pancakeHouseMenu = new Menu("PANCAKE HOUSE", "Breakfast");
MenuComponent dinnerMenu = new Menu("DINNER MENU", "Lunch");
MenuComponent cafeMenu = new Menu("CAFE MENU", "Dinner");
MenuComponent dessertMenu = new Menu("DESSERT MENU", "Dessert of course");
MenuComponent menus = new Menu("ALl MENUS", "All menus combined");
menus.add(pancakeHouseMenu);
menus.add(dinnerMenu);
menus.add(cafeMenu);
dinnerMenu.add(new MenuItem("意大利面", "这是意大利面", true, 1.1));
dinnerMenu.add(dessertMenu);
dessertMenu.add(new MenuItem("披萨", "这是披萨", true, 1.2));
Waitress waitress = new Waitress(menus);
waitress.printMenu();
}
}
2.2.7、执行结果
ALl MENUS, All menus combined
-----------------------------
PANCAKE HOUSE, Breakfast
-----------------------------
DINNER MENU, Lunch
-----------------------------
意大利面(素食), 1.1
-- 这是意大利面
DESSERT MENU, Dessert of course
-----------------------------
披萨(素食), 1.2
-- 这是披萨
CAFE MENU, Dinner
-----------------------------