一、了解组合模式
1.1 什么是组合模式
组合模式允许你将对象组合成树形结构来表现“整体/部分”层次机构。组合能够使客户以一致的方式处理个别对象以及组合对象。
组合模式让我们能够用树形方式创建对象的结构,树里面包含了组合以及个别的对象。
使用组合结构,我们能够把相同的操作应用在组合和个别对象上。换句话说,在大多数情况下,我们可以忽略对象组合和个别对象之间的差别。
1.2 组合模式组成结构
- Component :是组合中的对象声明接口,在适当的情况下,实现所有类共有接口的默认行为。声明一个接口用于访问和管理 Component 子部件。
- Leaf :在组合中表示叶子结点对象,叶子结点没有子结点。
- Composite :定义有枝节点行为,用来存储子部件,在 Component 接口中实现与子部件有关操作,如增加 (add) 和删除 (remove) 等。
1.3 组合模式 UML 图解
组合包含组件。组件有两种:组合和叶节点元素。组合持有一群孩子,这些孩子可以分别是别的组合或者叶节点元素。当你使用这种方式组织数据的时候,最终会得到树形结构 (由上而下的树形结构),根部是一个组合,而组合的分支逐渐向下延伸,直到叶节点为止。
1.4 组合模式应用场景
- 想表示对象的部分-整体层次结构(树形结构)。
- 希望用户忽略组合对象与单个对象的不同,用户将统一地使用组合结构中的所有对象。
二、组合模式具体应用
2.1 问题描述
打印菜单:通过代码实现打印早、中、晚餐的菜单,列出所有的菜品,并且在晚餐菜单中包含甜品菜单。
2.2 问题树形结构分析图
2.3 使用组合模式设计相关类图
2.4 代码实现
MenuComponent 抽象类
package com.jas.component;
/**
* @author Jas
* @create 2018-01-30 10:23
**/
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 double getPrice(){
throw new UnsupportedOperationException();
}
public void print(){
throw new UnsupportedOperationException();
}
}
组件 MenuItem 类
package com.jas.component;
/**
* 菜单项类实现,用来封装菜品信息
*
* getName() 方法返回菜品名字
* getPrice() 方法返回菜品价格
* print() 输出该菜品的详细信息
*
* @author Jas
* @create 2018-01-30 10:26
**/
public class MenuItem extends MenuComponent{
String name;
double price;
public MenuItem(String name, double price){
this.name = name;
this.price = price;
}
@Override
public String getName() {
return name;
}
@Override
public double getPrice() {
return price;
}
@Override
public void print() {
System.out.print(" " + getName());
System.out.println(" " + getPrice());
}
}
组件 Menu 类
package com.jas.component;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* 菜单类,
*
* getName() 方法返回具体菜单名
* add(MenuComponent menuComponent) 方法表示添加菜单
* remove(MenuComponent menuComponent) 删除一个菜单
* getChild(int i) 返回一个指定的菜单
* print() 用于输出菜单名和具体菜单菜品信息,也是所有代码中最难理解的一部分
*
* @author Jas
* @create 2018-01-30 10:30
**/
public class Menu extends MenuComponent {
/** menuComponents 用来保存早中晚的菜单项。 */
List<MenuComponent> menuComponents = new ArrayList<>();
/** name 表示菜单名 (早中晚甜品)*/
String name;
public Menu(String name){
this.name = name;
}
@Override
public void add(MenuComponent menuComponent) {
menuComponents.add(menuComponent);
}
@Override
public void remove(MenuComponent menuComponent) {
menuComponents.remove(menuComponent);
}
@Override
public MenuComponent getChild(int i) {
return menuComponents.get(i);
}
@Override
public String getName() {
return name;
}
/**
* 既可以输出菜单名,也可以输出菜单中的菜品名
*/
@Override
public void print() {
System.out.println("\n" + getName());
System.out.println("====================");
Iterator<MenuComponent> iterator = menuComponents.iterator();
while (iterator.hasNext()){
MenuComponent menuComponent = iterator.next();
menuComponent.print();
}
}
}
Waitress 类
package com.jas.component;
/**
* 招待员类
*
* @author Jas
* @create 2018-01-30 10:39
**/
public class Waitress {
MenuComponent allMenus;
public Waitress(MenuComponent allMenus){
this.allMenus = allMenus;
}
public void print(){
allMenus.print();
}
}
测试类
package com.jas.component;
/**
*测试类
*
* @author Jas
* @create 2018-01-30 10:40
**/
public class MenuTestDrive {
public static void main(String[] args) {
MenuComponent breakfastMenu = new Menu("早餐菜单");
MenuComponent lunchMenu = new Menu("午餐菜单");
MenuComponent dinnerMenu = new Menu("晚餐菜单");
MenuComponent dessertMenu = new Menu("甜品菜单");
/** 顶层菜单*/
MenuComponent allMenus = new Menu("ALL MENUS LIST");
breakfastMenu.add(new MenuItem("包子", 1.0));
breakfastMenu.add(new MenuItem("豆浆",2.0));
lunchMenu.add(new MenuItem("西红柿盖浇饭", 10.0));
lunchMenu.add(new MenuItem("西红柿鸡蛋面",10.0));
dinnerMenu.add(new MenuItem("热狗", 15.0));
dessertMenu.add(new MenuItem("苹果派", 5.0));
allMenus.add(breakfastMenu);
allMenus.add(lunchMenu);
allMenus.add(dinnerMenu);
/** 将甜品菜单添加在晚餐菜单中*/
dinnerMenu.add(dessertMenu);
Waitress waitress = new Waitress(allMenus);
waitress.print();
}
}
/**
* 输出
* ALL MENUS LIST
* ====================
*
* 早餐菜单
* ====================
* 包子 1.0
* 豆浆 2.0
*
* 午餐菜单
* ====================
* 西红柿盖浇饭 10.0
* 西红柿鸡蛋面 10.0
*
* 晚餐菜单
* ====================
* 热狗 15.0
*
* 甜品菜单
* ====================
* 苹果派 5.0
*
*/
三、组合模式总结
3.1 组合模式优缺点
优点
- 高层模块调用简单。
- 节点可自由增加。
缺点
在使用组合模式时,其叶子和树枝的声明都是实现类,而不是接口,违反了面对抽象编程和依赖倒置原则。
3.2 组合模式知识点总结
- 组合模式提供一个结构,可同时包容个别对象和组合对象。
- 组合模式允许客户对个别对象以及组合对象一视同仁。
- 组合结构内的任意对象称为组件,组件可以是组合,也可以是叶节点。
- 在使用组合模式时,要多加考虑方式,有的时候可以与迭代器模式配合使用。
PS:点击了解更多设计模式 http://blog.csdn.net/codejas/article/details/79236013
参考文献
《Head First 设计模式》