组合模式.
一、概念
- 组合模式:允许你将对象组合成树形结构来表现“整体/部分”层次结构。组合能让客户以一致的方式处理个别对象以及对象组合。
- 组合包含组件。组件有两种:组合和叶节点元素。组合持有一群孩子,这些孩子可以是别的组合或者叶节点元素。
- 角色:
组合部件(Component):它是一个抽象角色,为要组合的对象提供统一的接口。
叶子节点(Leaf):定义无子节点的行为,在组合中表示叶子节点对象,叶子节点不能有子节点。
组合节点(Composite):定义有子节点的行为,用来存储部件,实现在Component接口中的有关操作,如增加(Add)和删除(Remove)。
二、Demo 实现
TOPIC:我们的任务是想实现一个树形结构的菜单,如下~
1、组合部件
/**
* @Description: 组件的抽象类,菜单组件的角色是为叶节点和组合节点提供一个公共的接口。
* @author: cuixiuyin
* @date: 2018/12/29 08:59
*/
public abstract class MenuComponent {
/**
* @Description 组合节点(菜单)的方法组织在一起
* @author cuixiuyin
* @date 2018/12/29 09:03
*/
public void add(MenuComponent menuComponent) {
throw new UnsupportedOperationException();
}
public void remove(MenuComponent menuComponent) {
throw new UnsupportedOperationException();
}
public MenuComponent getChild(int i) {
throw new UnsupportedOperationException();
}
/**
* @Description 叶子节点(菜单项)的方法组织在一起、其中有一些也可以用于组合节点(菜单)上
* @author cuixiuyin
* @date 2018/12/29 09:03
*/
public String getName() {
throw new UnsupportedOperationException();
}
public String getDescription() {
throw new UnsupportedOperationException();
}
public Double getPrice() {
throw new UnsupportedOperationException();
}
public Boolean isVegetarian() {
throw new UnsupportedOperationException();
}
/**
* @Description 相关操作
* @author cuixiuyin
* @date 2019/01/02 09:04
*/
public void print() {
throw new UnsupportedOperationException();
}
}
组合部件为叶子节点和组合节点定义了统一的接口。所有的操作,如果子类没有实现,我们默认抛出一个 UnsupportedOperationException 异常。
为了要保持透明性,组合内所有的对象都必须实现这个接口,否则客户就必须操心哪个对象是用哪个接口,这就失去了组合模式的意义。
2、叶子节点
/**
* @Description: 菜单项 (叶子节点)
* @author: cuixiuyin
* @date: 2018/12/29 09:09
*/
public class MenuItem extends MenuComponent {
/**
* 名称
*/
private String name;
/**
* 描述
*/
private String description;
/**
* 是否为素食
*/
private Boolean vegetarian;
/**
* 价格
*/
private 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 Boolean isVegetarian() {
return vegetarian;
}
@Override
public Double getPrice() {
return price;
}
@Override
public void print() {
System.out.print(getName() + "--" + getDescription() + "," + getPrice() + ",");
if (isVegetarian()) {
System.out.print("(v)");
}
System.out.println();
}
}
叶子节点没有子节点,只有一些菜单项上的类目,不存在下一级结构的维护。
3、组合节点
/**
* @Description: 菜单(组合节点)
* @author: cuixiuyin
* @date: 2018/12/29 09:19
*/
public class Menu extends MenuComponent {
/**
* 可能持有菜单项或其他菜单
*/
List<MenuComponent> list = 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) {
list.add(menuComponent);
}
@Override
public void remove(MenuComponent menuComponent) {
list.remove(menuComponent);
}
@Override
public MenuComponent getChild(int i) {
return list.get(i);
}
@Override
public String getName() {
return name;
}
@Override
public String getDescription() {
return description;
}
/**
* @Description 递归 —— 不仅打印出菜单本身的信息,也打印出菜单内所有组件的内容:其他菜单和菜单项目。
* @author cuixiuyin
* @date 2018/12/29 09:26
*/
@Override
public void print() {
System.out.println("\n" + getName() + "," + getDescription());
System.out.println("--------------------");
Iterator<MenuComponent> iterator = list.iterator();
while (iterator.hasNext()) {
MenuComponent component = iterator.next();
component.print();
}
}
}
组合节点维护了一个集合,这个集合可以持有其他菜单项(叶子节点)或者菜单(组合节点)。
4、测试
public class Waitress {
private MenuComponent menuComponent;
public Waitress(MenuComponent menuComponent) {
this.menuComponent = menuComponent;
}
public void printMenu() {
menuComponent.print();
}
}
public class Test {
public static void main(String[] args) {
// 1、创建所有的菜单对象
MenuComponent breakfast = new Menu("早餐菜单", "Breakfast");
MenuComponent lunch = new Menu("午餐菜单", "Lunch");
MenuComponent dinner = new Menu("晚餐菜单", "Dinner");
MenuComponent dessert = new Menu("晚餐甜点", "Dessert");
// 2、创建顶层菜单
MenuComponent top = new Menu("顶层菜单", "Top");
top.add(breakfast);
top.add(lunch);
top.add(dinner);
// 3、晚餐菜单中加入甜点
dinner.add(dessert);
// 4、构建每份菜单中的菜单项(叶子节点)
breakfast.add(new MenuItem("dumplings", "饺子", false, 10.0));
breakfast.add(new MenuItem("bread", "面包", true, 7.0));
breakfast.add(new MenuItem("mile", "牛奶", false, 5.0));
lunch.add(new MenuItem("rice", "米饭", true, 2.0));
lunch.add(new MenuItem("burger", "汉堡", false, 6.0));
lunch.add(new MenuItem("vegetables", "蔬菜", true, 1.0));
dinner.add(new MenuItem("noodle", "面条", true, 4.0));
dessert.add(new MenuItem("cake", "蛋糕", true, 3.0));
// 5、把最顶层的组合给服务员,以便可以获取到所有订单
Waitress waitress = new Waitress(top);
waitress.printMenu();
}
}
5、组合迭代器 — 组合模式和迭代器模式的配合
/**
* @Description: 组合迭代器,遍历整个树结构
* @author: cuixiuyin
* @date: 2018/12/29 21:30
*/
public class CompositeIterator implements Iterator {
private Stack<Iterator> stack = new Stack();
public CompositeIterator(Iterator iterator) {
stack.push(iterator);
}
@Override
public boolean hasNext() {
if (stack.empty()) {
return false;
}
Iterator peek = stack.peek();
if (peek.hasNext()) {
return true;
}
stack.pop();
return hasNext();
}
@Override
public MenuComponent next() {
if (hasNext()) {
Iterator peek = stack.peek();
MenuComponent component = (MenuComponent) peek.next();
if (component instanceof Menu) {
stack.push(component.createIterator());
}
return component;
}
return null;
}
}
该组合迭代器,从选定的节点开始,往下遍历整个树形结构,选取符合条件的数据。亲测,该迭代器只适合做筛选的操作,不适合做整体的操作,因为会有重复的步骤。
三、总结
- 迭代器模式:提供一种方法顺序访问一个聚合对象中的各个元素,而又不暴露其内部的表示。
- 迭代器模式的另一个意义在于:把游走的任务放在迭代器上,而不是聚合上。这样简化了聚合的接口和实现,也让责任各得其所。
- 单一职责原则:一个类应该只有一个引起变化的原因。类的每个责任都有改变的潜在区域,超过一个责任,意味着超过一个改变的区域。因此,尽量让每个类保持单一责任。
- 单一职责原则听起来很容易,但其实做起来并不简单:区分设计中的责任,是最困难的事情之一。我们的大脑很习惯看着一大群的行为,然后将它们集中在一起,尽管他们可能属于两个或多个不同的责任。想要成功的唯一方法,就是努力不懈地检查你的设计,随着系统的成长。随时观察有没有迹象显示某个类改变的原因超过一个。
- 组合模式特别适用于树形结构,假设我们有了一个树形结构的菜单、子菜单和可能还带有菜单项的子菜单,那么任何一个菜单都是一种“组合”。因为它既可以包含其他菜单,也可以包含菜单项,甚至个别对象只是菜单项 —— 并未持有其他对象。
- 组合模式是违反单一职责原则的,这是一个典型的折衷案例。组合模式以单一责任设计原则换取换取透明性。什么是透明性?通过让组件的接口同时包含一些管理子节点和叶节点的操作,客户就可以将组合和叶节点一视同仁。也就是说,一个元素究竟是组合还是叶节点,对客户是透明的。为了维护透明性,每一个对象都使有相同的接口,难免组合中有些对象的行为不太一样,我们可以选择返回 null 或者 false,甚至是直接抛出异常。
- 组合模式的优点:
1、组合模式让我们能用树形方式创建对象的结构,树里面包含了组合以及个别的对象(叶节点元素)。
2、使用组合结构,我们能把相同的操作应用在组合和个别对象(叶节点元素)上。换句话说,在大多数情况下,我们可以忽略对象组合和个体对象(叶节点元素)之间的差别。 - 组合模式的使用场景:
1、当想表达对象的“整体/部分”的层次结构时。
2、希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象时。 - 在实现组合模式时,有许多设计上的折衷。你要根据需要平衡透明性和安全性。