一、组合模式的概念
组合模式,将对象组合成树形结构以表示“部分-整体”的层次结构,组合模式使得用户对单个对象和组合对象的使用具有一致性。掌握组合模式的重点是要理解清楚 “部分/整体” 还有 ”单个对象“ 与 "组合对象" 的含义。
听懂了这句话就不用往下看了,说明你会了。
听不懂我觉得也正常,如果用一句话能学会就没人看书了。像我这种笨人,都是学会了一个模式,然后往它的定义上套。
二、什么时候使用组合模式
- 需要某种树形结构,可以容纳菜单、子菜单和菜单项;
- 需要能够在各个菜单之间随意游走,同级的菜单类型可以不同;
- 需要能够在遍历时,随意遍历各个菜单;

也就是说,树形结构中的菜单,子菜单是容器,菜单项是内容。
组合模式是要让容器和内容具有一致性,创造出递归结构,并且在某一个场景下,容器和内容可以无差别使用,图中第二层既可以是Leaf,也可以是Node。
常见的场景有:部门树、功能菜单树、字典树等等,在遇到可递归的树形结构需求时,都可以考虑一下是否需要使用组合模式。
三、怎么使用组合模式
以一个需求为例进行说明,还是汽车相关的。
汽车厂商将汽车分为品牌、车系、车型、他们之前是属性关系,品牌下有多个车系,车系下有多个车型,具体例子:

品牌是奔驰。
奔驰下有多个车系,A级,C级,E级。
每个车系下有多个车型,A级下有A180,A200,A220。
需求是,打印出所有的车系和车型,在打印时进行名称美化,传入车系和车型都可以完成名称美化。
3.1 不使用组合模式带来的弊端

不使用组合模式来创建这个结构。
- 首先创建一个品牌对象(Brand),里面有一个车系(Series)的ArrayList集合;
- 然后创建一个车系对象(Series),里面有一个车型(Model)的ArrayList集合;
- 最后创建一个车系对象(Model);
一个典型的一对多关系,具体代码如下:
/**
* 品牌
*/
public class Brand {
private List<Series> series = new ArrayList<Series>();
private String name;
public Brand(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public void add(Series s) {
series.add(s);
}
public void remove(Series s) {
series.remove(s);
}
public Series getChild(int i) {
return series.get(i);
}
public void print() {
System.out.println(getName());
Iterator<Series> iterator = series.iterator();
while(iterator.hasNext()) {
iterator.next().print();
}
}
}
/**
* 车系
*/
public class Series {
private List<Model> model = new ArrayList<Model>();
private String name;
public Series(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public void add(Model m) {
model.add(m);
}
public void remove(Model m) {
model.remove(m);
}
public Model getChild(int i) {
return model.get(i);
}
public void print() {
System.out.println(getName());
Iterator<Model> iterator = model.iterator();
while (iterator.hasNext()) {
iterator.next().print();
}
}
}
/**
* 车型
*/
public class Model {
private String name;
public Model(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public void print() {
System.out.println(getName());
}
}
/**
* 运行输出
* 奔驰
* 奔驰A系
* A100
* A200
* 奔驰C系
* C100
* C200
*/
public class Client {
public static void main(String[] args) {
Brand brand = new Brand("奔驰");
Series series = new Series("奔驰A系");
brand.add(series);
Model a100 = new Model("A100");
Model a200 = new Model("A200");
series.add(a100);
series.add(a200);
Series series1 = new Series("奔驰C系");
brand.add(series1);
Model c100 = new Model("C100");
Model c200 = new Model("C200");
series1.add(c100);
series1.add(c200);
brand.print();
}
}
需求是,打印出所有的车系和车型,在打印时进行名称美化,传入车系和车型都可以完成名称美化。
第一个弊端 — 通用性差
这里要写这个函数就有问题,由于车系和车型的类型不同,所以要单独编写函数,实现基本一致:
public String beautiful(Series series) {
return "★" + series.getName() + "★";
}
public String beautiful(Model model) {
return "★" + model.getName() + "★";
}
第二个弊端 — 结构不灵活
现在需求有变更:
车系是一个树形结构,也就是说Series对象的子对象中,可以存放车型,也可以存放车系。
回顾一下Series的代码:
public class Series {
private List<Model> model = new ArrayList<Model>();
......
}
用于存放子对象的集合,泛型类型是Model,不能保存Series对象。
3.2 使用组合模式带来解决这个问题
直接上类图,使用组合模式后的类图如下:

回顾概念:
- 将对象组合成树形结构以表示“部分-整体”的层次结构
- 组合模式使得用户对单个对象和组合对象的使用具有一致性
现在,品牌、车系、车型都继承自组件(Component)。品牌中的集合,泛型类型是Component,车系中的集合泛型类型也是Component。这样就做到了概念中的两点,对象还是树形结构来表示部分和整体。同时,由于对象本身继承自Component,它内部的子对象集合也是Component,所以做到了一致性。
下面是组合模式下的代码:
/**
* 组件对象
*/
public abstract class Component {
public String getName() {
throw new UnsupportedOperationException();
}
public void add(Component component) {
throw new UnsupportedOperationException();
}
public void remove(Component component) {
throw new UnsupportedOperationException();
}
public Component getChild(int i) {
throw new UnsupportedOperationException();
}
public void print() {
throw new UnsupportedOperationException();
}
public String beautiful(Component component) {
return "★" + component.getName() + "★";
}
}
/**
* 品牌,继承自Component
*/
public class Brand extends Component {
// 内部不在是存放具体类型,现在改成了Component类型,继承自Component的对象都可以加入
private List<Component> series = new ArrayList<Component>();
private String name;
public Brand(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public void add(Component component) {
series.add(component);
}
public void remove(Component component) {
series.remove(component);
}
public Component getChild(int i) {
return series.get(i);
}
public void print() {
System.out.println(getName());
Iterator<Component> iterator = series.iterator();
while(iterator.hasNext()) {
iterator.next().print();
}
}
}
/**
* 车系,继承自Component
*/
public class Series extends Component {
// 内部不在是存放具体类型,现在改成了Component类型,继承自Component的对象都可以加入
private List<Component> model = new ArrayList<Component>();
private String name;
public Series(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public void add(Component component) {
model.add(component);
}
public void remove(Component component) {
model.remove(component);
}
public Component getChild(int i) {
return model.get(i);
}
public void print() {
System.out.println(beautiful(this));
Iterator<Component> iterator = model.iterator();
while (iterator.hasNext()) {
iterator.next().print();
}
}
}
/**
* 车型
*/
public class Model extends Component {
private String name;
public Model(String name) {
this.name = name;
}
public String getName() {
return this.name;
}
public void print() {
System.out.println(beautiful(this));
}
}
组合模式的使用方式:
public class Client {
public static void main(String[] args) {
// 创建品牌
Component brand = new Brand("奔驰");
// 创建车系A
Component series = new Series("奔驰A系");
// 把车系A加到品牌
brand.add(series);
// 创建两个车型
Component a100 = new Model("A100");
Component a200 = new Model("A200");
series.add(a100);
series.add(a200);
// 创建车系C
Component series1 = new Series("奔驰C系");
// 加到品牌
brand.add(series1);
// 在创建2个车型添加到车系C
Component c100 = new Model("C100");
Component c200 = new Model("C200");
series1.add(c100);
series1.add(c200);
// 这里!在车型c100和c200平级的节点,创建一个子车系
Component series2 = new Series("奔驰C系运动款");
series1.add(series2);
// 在子车系下创建2个车型
Component c100s = new Model("C100S");
Component c200s = new Model("C200S");
series2.add(c100s);
series2.add(c200s);
// 输出整个的树
brand.print();
}
}
// 输出结果
奔驰
★奔驰A系★
★A100★
★A200★
★奔驰C系★
★C100★
★C200★
★奔驰C系运动款★
★C100S★
★C200S★
用组合模式解决了需求中的2个点:
1.使用一个函数操作车系,车型
// 可以使用一个方式来操作车系、车型、品牌等对象,只要是这个树结构下的节点均可以操作
public String beautiful(Component component) {
return "★" + component.getName() + "★";
}
2.随时可以添加子节点
// 在创建2个车型添加到车系C
Component c100 = new Model("C100");
Component c200 = new Model("C200");
series1.add(c100);
series1.add(c200);
// 这里!在车型c100和c200平级的节点,创建一个子车系
Component series2 = new Series("奔驰C系运动款");
输出结果是:
★奔驰C系★(车系)
|-★C100★(车型)
|-★C200★(车型)
|-★奔驰C系运动款★(车系)
|-★C100S★(车型)
|-★C200S★(车型)
树的任意一级可以存放任意的对象(只要是继承自Component),这种方式解决了第二个需求。
四、总结
组合模式,我的一个简单理解是,在两个场景下应该去考虑使用:
- 当你的对象结构是树,树的各个层级并不是有一个对象组合而成的,但这些对象都有相同的业务操作(上面的例子中的display方法);
- 面相后续的需求,有可能每一层保存的对象类型也不一定相同(例如上面的例子中,车系下不只有车型,还有子车系);
组合模式的做法就很简单了,主要也是两点:
- 树上的所有节点都继承一个Component对象(抽象对象);
- 对象种的ArrayList的泛型对象使用Component;
以上就是组合模式的一些理解, 有不足之处请大家矫正,谢谢。
来源:oschina
链接:https://my.oschina.net/u/2450666/blog/3225372