定义
组合(Composite)模式:又称作部分整体模式,它是一种将对象组合成树状的层次结构的模式,用来表示 “部分-整体” 的关系,使用户对单个对象和组合对象具有一致的访问性。
组合模式属于对象结构型模式。
要点
组合模式的优点:
- 组合模式使得客户端代码可以一致地处理单个对象和组合对象,无须关心自己处理的是单个对象,还是组合对象,这简化了客户端代码;
- 更容易在组合体内加入新的对象,客户端不会因为加入了新的对象而更改源代码,满足“开闭原则”;
组合模式的缺点:
- 设计较复杂,客户端需要花更多时间理清类之间的层次关系;
- 不容易限制容器中的构件;
- 不容易用继承的方法来增加构件的新功能;
组合模式包含以下主要角色:
抽象构件(Component):它的主要作用是为树叶构件和树枝构件声明公共接口,并实现它们的默认行为。在透明式的组合模式中抽象构件还声明访问和管理子类的接口;在安全式的组合模式中不声明访问和管理子类的接口,管理工作由树枝构件完成。
树叶构件(Leaf):是组合中的叶节点对象,它没有子节点,用于实现抽象构件角色中声明的公共接口。
树枝构件(Composite):是组合中的分支节点对象,它有子节点。它实现了抽象构件角色中声明的接口,它的主要作用是存储和管理子部件,通常包含 Add()、Remove()、GetChild() 等方法。
例如,要一个连锁饭店有三个餐厅 —— 煎饼屋、正式餐厅和咖啡厅,它们各自有自己的菜单和菜单项,每个菜单还可能包含子菜单:
场景
假如李先生到超市购物,用 1 个红色小袋子装了 2 包纯牛奶(单价 7.9 元)、1 袋精品食盐(单价 9.9 元);用 1 个白色小袋子装了 2 瓶白酒(单价 68 元)和 3 包龙井茶(单价 80 元);用 1 个中号蓝袋子装了前面的红色小袋子和 1 个名牌电炒锅(单价 380 元);用 1 个大号黑袋子装了前面的中号蓝袋子、白色小袋子和 1 双运动鞋(单价 198 元),要求编程显示李先生放在大袋子中的所有商品信息并计算要支付的总价。
实现
Item
/**
* 抽象物品,相当于 Component
*/
public interface Item {
/**
* 计算物品价格
*/
double calculate();
/**
* 展示物品详情
*/
void show();
}
Goods
/**
* 商品,相当于叶子节点
*/
public class Goods implements Item {
/**
* 商品名称
*/
private String goodsName;
/**
* 商品单价
*/
private double price;
/**
* 商品数量
*/
private int quantity;
public Goods(String goodsName, double price, int quantity) {
this.goodsName = goodsName;
this.price = price;
this.quantity = quantity;
}
@Override
public double calculate() {
return price * quantity;
}
@Override
public void show() {
System.out.println(String.format("%s (数量:%d,单价:%f)", goodsName, quantity, price));
}
}
Bag
/**
* 袋子,相当于树枝节点
*/
public class Bag implements Item{
/**
* 袋子名称
*/
private String bagName;
/**
* 袋子中的商品
*/
private ArrayList<Item> goodsList = new ArrayList<>();
public Bag(String bagName) {
this.bagName = bagName;
}
/**
* 添加商品
*/
public void add(Item item) {
goodsList.add(item);
}
/**
* 删除商品
*/
public void remove(Item item) {
goodsList.remove(item);
}
/**
* 获取商品
*/
public Item getChild(int i) {
return goodsList.get(i);
}
@Override
public double calculate() {
float totalPrice = 0;
for(Item item : goodsList) {
totalPrice += item.calculate();
}
return totalPrice;
}
@Override
public void show() {
for (Item item : goodsList) {
item.show();
}
}
}
Client
public class Client {
public static void main(String[] args) {
Bag redSmallBag = new Bag("红色小袋子");
redSmallBag.add(new Goods("纯牛奶", 7.9, 2));
redSmallBag.add(new Goods("精品食盐", 9.9, 1));
Bag whiteSmallBag = new Bag("白色小袋子");
whiteSmallBag.add(new Goods("白酒", 68, 2));
whiteSmallBag.add(new Goods("龙井茶", 80, 3));
Bag blueMediumBag = new Bag("蓝色中号袋子");
blueMediumBag.add(redSmallBag);
blueMediumBag.add(new Goods("名牌电炒锅", 380, 1));
Bag blackBigBag = new Bag("黑色大号袋子");
blackBigBag.add(blueMediumBag);
blackBigBag.add(whiteSmallBag);
blackBigBag.add(new Goods("运动鞋", 198, 1));
System.out.println(String.format("商品总价是:%f\n", blackBigBag.calculate()));
System.out.println("商品详情是:");
blackBigBag.show();
}
}
==================================
输出:
商品总价是:979.700000
商品详情是:
纯牛奶 (数量:2,单价:7.900000)
精品食盐 (数量:1,单价:9.900000)
名牌电炒锅 (数量:1,单价:380.000000)
白酒 (数量:2,单价:68.000000)
龙井茶 (数量:3,单价:80.000000)
运动鞋 (数量:1,单价:198.000000)
源码
总结
- 在需要表示一个对象整体与部分的层次结构的场合
- 要求对用户隐藏组合对象与单个对象的不同,用户可以用统一的接口使用组合结构中的所有对象的场合
- 简化客户端操作。客户端只需要面对一致的对象而不用考虑整体部分或者节点叶子的问题
- 具有较强的扩展性。当我们要更改组合对象时,我们只需要调整内部的层次关系,客户端不用做出任何改动.
- 方便创建出复杂的层次结构。客户端不用理会组合里面的组成细节,容易添加节点或者叶子从而创建出复杂的树形结构
- 需要遍历组织机构,或者处理的对象具有树形结构时, 非常适合使用组合模式
- 要求较高的抽象性,如果节点和叶子有很多差异性的话,比如很多方法和属性都不一样,不适合使用组合模式
Java AWT/Swing 中的简单组件 JTextComponent 有子类 JTextField、JTextArea,容器组件 Container 也有子类 Window、Panel。其中涉及到了组合模式。