文章目录
- 简介
- 场景
- 解决方案
- 代码
- 总结
简介
组合模式是一种结构型设计模式,你可以使用它把对象组合成树状结构,并且能像使用单独的对象一样使用它们。
如果应用的核心模型能用树状结构表示,在应用中使用组合模式才有价值。
场景
比如,你有两类对象:产品和盒子。一个盒子中可以包含多个产品或者几个较小的盒子。这些小盒子中同样可以包含一些产品或更小的盒子,以此类推。
假设你希望在这些类的基础上开发一个订单系统。订单中可以包含无包装的简单产品,也可以包含装满产品的盒子……以及其他盒子。这个时候你会怎么计算每个订单的总价格呢?
订单中可能包括各种产品,这些产品放在盒子中,然后又被放入一层又一层更大的盒子中。整个结构看上去像是一棵倒过来的树。
你可以尝试直接计算:打开所有盒子,找到每件产品,然后计算总价。这在真实世界中可能是可以的,但是在程序里,并不能简单地使用循环语句来完成这个工作。因为你必须事先知道所有产品和盒子的类别,所有盒子的嵌套层数以及其他繁杂的细节信息。所以直接计算极不方便,甚至完全不可行。
有人可能会想到用递归来解决,其实组合模式也用的是类似这种方式,把大问题拆解成小问题。
解决方案
组合模式建议使用一个通用接口来跟 产品和盒子交互,并且在这个接口中声明一个计算总价的方法。
对于一个产品,这个方法直接返回他的价格;对于一个盒子,这个方法遍历盒子里所有的项目,询问每个项目的价格,然后返回盒子的总价格。如果其中某个项目是小一号的盒子,那么当前盒子也会遍历其中的所有项目,以此类推,直到计算出所有内部组成部分的价格。你甚至可以在盒子的最终价格中增加额外费用,作为这个盒子的包装费用。
这种方式的最大优点就在于你不需要了解构成树状结构的对象的具体类。你也不需要了解对象是简单的产品还是复杂的盒子。你只需要调用通用接口来以相同的方式对它进行处理就可以了。当你调用这个方法之后,对象会把请求沿着树结构传递下去。
代码
/* 组合元素基类(公共接口) */
interface OrderComponent {double calculatePrice(); // 核心计算方法定义
}/* 商品项实现类 */
class ProductItem implements OrderComponent {private double unitPrice;private int quantity;public ProductItem(double price, int qty) {this.unitPrice = price;this.quantity = qty;}@Overridepublic double calculatePrice() {return unitPrice * quantity; // 基本商品价格计算逻辑}
}/* 组合容器(有包装附加费) */
class CompositeBox implements OrderComponent {private List<OrderComponent> contents = new ArrayList<>();private double packagingFee;public CompositeBox(double fee) {this.packagingFee = fee; // 包装费扩展}public void add(OrderComponent item) {contents.add(item);}@Overridepublic double calculatePrice() {double total = packagingFee; // 包装费基数for (OrderComponent item : contents) {// 递归计算嵌套内容(树形结构处理)total += item.calculatePrice(); }return total;}
}// 使用示例(典型场景)
class Client {public static void main(String[] args) {// 创建基础商品OrderComponent phone = new ProductItem(2999.0, 2);OrderComponent earphone = new ProductItem(199.0, 1);// 构建礼品套装盒子(含子包装)CompositeBox giftBox = new CompositeBox(50.0); giftBox.add(new ProductItem(399.0, 1)); // 赠品数据线giftBox.add(earphone);// 主订单容器(包含嵌套包装)CompositeBox mainOrder = new CompositeBox(20.0);mainOrder.add(phone);mainOrder.add(giftBox);System.out.println("总金额:" + mainOrder.calculatePrice());}
}
总结
- 组件(Component)接口:描述了树里面简单项目和复杂项目所共有的操作。
- 叶节点(Leaf) 是树的基本结构,它不包含子项目。一般情况下,叶节点最终会完成大部分的实际工作,因为它们没办法把工作指派给其他部分。
- 容器(Container)——又叫“组合(Composite)”——它包含叶节点或其他容器。容器不知道他的子项目所属的具体类,它只通过通用的组件接口和子项目交互。容器接收到请求后会把工作分配给自己的子项目,处理中间结果,然后把最终结果返回给客户端。
- 客户端(client)通过组件接口和所有项目交互。因此,客户端能用相同方式跟树状结构中的简单或复杂项目进行交互。