定义:允许你将对象组合成树形结构来表现“整体/部分”层次结构。组合能让客户以一致的方式处理个别对象以及对象组合。
类图:
一个小例子
(1)创建组合迭代器
/**
* 迭代器,遍历组件中的菜单项,而且确保所有的子菜单(以及子子菜单)都被包括进来
*/
public class CompositeIterator implements Iterator<MenuComponent> {
Stack<Iterator<MenuComponent>> stack = new Stack<Iterator<MenuComponent>>();
// 将要遍历的顶层组合的迭代器传入。我们把它抛入一个堆栈数据结构中
public CompositeIterator(Iterator<MenuComponent> iterator) {
stack.push(iterator);
}
// 返回组件
public MenuComponent next() {
if (hasNext()) {
Iterator<MenuComponent> iterator = stack.peek();
MenuComponent component = iterator.next();
stack.push(component.createIterator());
return component;
} else {
return null;
}
}
// 判断是否还有下一个
public boolean hasNext() {
if (stack.empty()) {
return false;
} else {
Iterator<MenuComponent> iterator = stack.peek();
if (!iterator.hasNext()) {
stack.pop();
return hasNext();
} else {
return true;
}
}
}
}
(2)创建空迭代器
/**
* 空迭代器
*/
public class NullIterator implements Iterator<MenuComponent> {
public MenuComponent next() {
return null;
}
public boolean hasNext() {
return false;
}
}
(3)创建一个抽象菜单组件
/**
* 菜单组件,抽象类,菜单和菜单项都要继承它
*/
public abstract class MenuComponent {
public void add(MenuComponent menuComponent) {
throw new UnsupportedOperationException();
}
public void remove(MenuComponent menuComponent) {
throw new UnsupportedOperationException();
}
public MenuComponent getChild(int i) {
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 abstract Iterator<MenuComponent> createIterator();
public void print() {
throw new UnsupportedOperationException();
}
}
(4)创建菜单(组合节点)
/**
* 菜单(组合节点)
*/
public class Menu extends MenuComponent {
Iterator<MenuComponent> iterator = null;
ArrayList<MenuComponent> menuComponents = new ArrayList<MenuComponent>();
String name;
String description;
public Menu(String name, String description) {
this.name = name;
this.description = description;
}
public void add(MenuComponent menuComponent) {
menuComponents.add(menuComponent);
}
public void remove(MenuComponent menuComponent) {
menuComponents.remove(menuComponent);
}
public MenuComponent getChild(int i) {
return menuComponents.get(i);
}
public String getName() {
return name;
}
public String getDescription() {
return description;
}
public Iterator<MenuComponent> createIterator() {
if (iterator == null) {
iterator = new CompositeIterator(menuComponents.iterator());
}
return iterator;
}
public void print() {
System.out.print("\n" + getName());
System.out.println(", " + getDescription());
System.out.println("---------------------");
Iterator<MenuComponent> iterator = menuComponents.iterator();
while (iterator.hasNext()) {
MenuComponent menuComponent = iterator.next();
menuComponent.print();
}
}
}
(5)创建菜单项(即叶子节点)
/**
* 菜单项,也就是叶子节点
*/
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;
}
public String getName() {
return name;
}
public String getDescription() {
return description;
}
public double getPrice() {
return price;
}
public boolean isVegetarian() {
return vegetarian;
}
/**
* 叶子节点不会有下一级,所以不需要遍历,返回空迭代器
*/
public Iterator<MenuComponent> createIterator() {
return new NullIterator();
}
public void print() {
System.out.print(" " + getName());
if (isVegetarian()) {
System.out.print("(v)");
}
System.out.println(", " + getPrice());
System.out.println(" -- " + getDescription());
}
}
(6)创建客户(一个女招待)
/**
* 女招待(客户)
*/
public class Waitress {
MenuComponent allMenus;
// 只和MenuComponent组件交流,可以说很简单了
public Waitress(MenuComponent allMenus) {
this.allMenus = allMenus;
}
public void printMenu() {
allMenus.print();
}
public void printVegetarianMenu() {
Iterator<MenuComponent> iterator = allMenus.createIterator();
System.out.println("\nVEGETARIAN MENU\n----");
while (iterator.hasNext()) {
MenuComponent menuComponent = iterator.next();
try {
if (menuComponent.isVegetarian()) {
menuComponent.print();
}
} catch (UnsupportedOperationException e) {}
}
}
}
(7)测试类
public class MenuTestDrive {
public static void main(String args[]) {
MenuComponent pancakeHouseMenu =
new Menu("煎饼屋菜单", "早餐");
MenuComponent dinerMenu =
new Menu("午餐菜单", "午餐");
MenuComponent cafeMenu =
new Menu("咖啡菜单", "晚餐");
MenuComponent dessertMenu =
new Menu("甜点菜单", "甜点类");
MenuComponent allMenus = new Menu("全部菜单", "包含全部菜单");
allMenus.add(pancakeHouseMenu);
allMenus.add(dinerMenu);
allMenus.add(cafeMenu);
// 可以添加菜单项
pancakeHouseMenu.add(new MenuItem(
"鸡蛋饼",
"用鸡蛋和面粉煎成的",
true,
2.99));
dinerMenu.add(new MenuItem(
"辣椒炒肉",
"用辣椒和肉炒成的",
true,
2.99));
// 也可以添加菜单
dinerMenu.add(dessertMenu);
dessertMenu.add(new MenuItem(
"甜酒汤圆",
"用甜酒和汤圆做成的",
true,
1.59));
cafeMenu.add(new MenuItem(
"卡布奇若",
"用咖啡豆等东西做成的",
true,
3.99));
Waitress waitress = new Waitress(allMenus);
waitress.printVegetarianMenu();
}
}
(8)输出
VEGETARIAN MENU
----
鸡蛋饼(v), 2.99
-- 用鸡蛋和面粉煎成的
辣椒炒肉(v), 2.99
-- 用辣椒和肉炒成的
甜酒汤圆(v), 1.59
-- 用甜酒和汤圆做成的
卡布奇若(v), 3.99
-- 用咖啡豆等食品做成的
在测试的时候,你需要哪些菜单就创建哪些菜单对象,然后每个菜单对象可以通过add()方法添加任意数量的菜单项(叶子节点)或菜单(组合节点),然后输出的时候通过迭代器会遍历这个树形结构(就是从上至下的树形结构),打印出每一个菜单项的详细信息,这就很有弹性,很自由了。
透明性:
这里,菜单组件既要管理层次结构,而且还有执行菜单的操作,这就违反了“一个类,一个责任”的设计原则。但是组合模式以单一责任设计模式换取透明性。
透明性就是通过让组件的接口同时包含一些管理子节点和叶节点的操作,客户就可以将组合和叶节点一视同仁。也就是说,一个元素究竟是组合还是叶节点,对客户是透明的。
现在回过头来理解组合模式的定义,我们可以知道:
所谓的“整体和部分”,拿图形用户界面来说,你经常会看到一个顶层的组件(像是Frame或Panel)包含着其他组件(像菜单,文字面板,滚动条,按钮),所以你的GUI包含了若干部分,但是当你显示它的时候,你认为它是一个整体。你告诉顶层的组件显示,然后就放手不管,由顶层组件负责显示所有相关的部分。
我们称这种包含其他组件的组件为组合对象,而称没有包含其他组件的组件为叶节点对象。
所谓的“一致性的方式”就是说,组合对象和叶节点对象之间具有共同的方法可以调用,我们可以叫组合对象显示或叫叶节点对象显示,他们会各自做出最正确的事情。组合对象会叫它所有的组件显示。这样一来,就意味着,每个对象都有相同的接口,但是有可能组合中有些对象的行为不太一样,这样处理就不太合适。但是,为了保持透明性,组合内所有的对象都必须实现相同的接口,否者用户就要操心哪个对象是用哪个接口,这就失去了组合模式的意义。很明显的,这样意味着有些对象具备一个没有意义的方法调用。解决这个问题的一个方法是:让这些没有意义的方法不做事,或返回null或false,甚至抛出异常。