定义
将对象组合成树形结构以表示“部分-整体”的层次结构。
作用
组合模式使客户端对单个对象和组合对象保持一致的方式处理
类型
结构型
例子
菜单、目录
适用场景
- 希望客户端可以忽略组合对象和单个对象的差异时
- 处理一个树形结构时
优点
- 清楚地定义分层次的复杂对象,表示对象的全部或部分层次
- 让客户端忽略层次的差异,方便对整个层次结构进行控制
- 简化客户端代码
- 符合开闭原则
缺点
- 限制类型时会较为复杂
- 使设计变得更加抽象
相关设计模式
- 组合模式和访问者模式
coding
业务场景:
视频网站有很多课程和目录,课程也有价格。比如java课程分属于java这个目录下,如果我们使课程的目录、课程都继承同一个抽象类,那我们就可以把课程本身和课程目录视为同一类对象进行操作。操作上还是会有些差别,具体的再定制化处理。例如打印这个动作,可以打印课程,也可以打印目录
课程、目录的父类
/**
* 目录组件
**/
public abstract class CatalogComponent {
public void add(CatalogComponent catalogComponent){
throw new UnsupportedOperationException("不支持添加操作");
}
public void remove(CatalogComponent catalogComponent){
throw new UnsupportedOperationException("不支持删除操作");
}
public void getName(CatalogComponent catalogComponent){
throw new UnsupportedOperationException("不支持获取名称操作");
}
public void getPrice(CatalogComponent catalogComponent){
throw new UnsupportedOperationException("不支持获取价格操作");
}
public void print(CatalogComponent catalogComponent){
throw new UnsupportedOperationException("不支持打印操作");
}
}
课程类
/**
* 课程
**/
public class Course extends CatalogComponent {
private String name;
private double price;
public Course(String name,double price){
this.name=name;
this.price=price;
}
@Override
public String getName(CatalogComponent catalogComponent) {
return this.name;
}
@Override
public double getPrice(CatalogComponent catalogComponent) {
return this.price;
}
@Override
public void print() {
System.out.println("Course Name:"+this.name+" Price:"+this.price);
}
}
课程目录
/**
* 课程目录
**/
public class CourseCatalog extends CatalogComponent{
private List<CatalogComponent> items=new ArrayList<>();
private String name;
public CourseCatalog(String name){
this.name=name;
}
@Override
public void add(CatalogComponent catalogComponent) {
items.add(catalogComponent);
}
@Override
public void remove(CatalogComponent catalogComponent) {
items.remove(catalogComponent);
}
@Override
public void print() {
System.out.println(this.name);
for(CatalogComponent catalogComponent:items){
System.out.print(" ");
catalogComponent.print();
}
}
}
UML类图
应用类
/**
* 应用类
**/
public class Test {
public static void main(String[] args) {
//操作系统课程
CatalogComponent linuxCourse=new Course("Linux课程",11);
CatalogComponent windowCouse=new Course("Windows课程",12);
//java课程目录
CatalogComponent javaCourseCatalog=new CourseCatalog("java课程目录");
CatalogComponent mallCourse1=new Course("java电商一期课程",55);
CatalogComponent mallCourse2=new Course("java电商二期课程",66);
CatalogComponent designPattern=new Course("java电商二期课程",77);
javaCourseCatalog.add(mallCourse1);
javaCourseCatalog.add(mallCourse2);
javaCourseCatalog.add(designPattern);
//主目录
CatalogComponent mainCourseCatalog=new CourseCatalog("课程主目录");
mainCourseCatalog.add(linuxCourse);
mainCourseCatalog.add(windowCouse);
mainCourseCatalog.add(javaCourseCatalog);
//主目录打印
mainCourseCatalog.print();
}
}
运行结果
课程主目录
Course Name:Linux课程 Price:11.0
Course Name:Windows课程 Price:12.0
java课程目录
Course Name:java电商一期课程 Price:55.0
Course Name:java电商二期课程 Price:66.0
Course Name:java电商二期课程 Price:77.0
看到主目录和java课程目录虽然分了层级,但是java课程和java课程目录并没有分层级,怎么办?打印时候区分目录下是课程还是目录,是课程就两个\t开始,因此我们就要进行动态类型判断了,这也就是组合模式的缺点。
如果目录大于2级,那加几个\t也就不固定了,因此我们需要在目录里加上一个level(层级)属性
/**
* 课程目录
**/
public class CourseCatalog extends CatalogComponent{
private List<CatalogComponent> items=new ArrayList<>();
private String name;
private Integer level;
public CourseCatalog(String name,Integer level){
this.name=name;
this.level=level;
}
@Override
public void add(CatalogComponent catalogComponent) {
items.add(catalogComponent);
}
@Override
public void remove(CatalogComponent catalogComponent) {
items.remove(catalogComponent);
}
@Override
public void print() {
System.out.println(this.name);
for(CatalogComponent catalogComponent:items){
if(this.level!=null) {
for(int i=0;i<this.level;i++) {
System.out.print(" ");
}
}
catalogComponent.print();
}
}
}
/**
* 应用类
**/
public class Test {
public static void main(String[] args) {
//操作系统课程
CatalogComponent linuxCourse=new Course("Linux课程",11);
CatalogComponent windowCouse=new Course("Windows课程",12);
//java课程目录
CatalogComponent javaCourseCatalog=new CourseCatalog("java课程目录",2);
CatalogComponent mallCourse1=new Course("java电商一期课程",55);
CatalogComponent mallCourse2=new Course("java电商二期课程",66);
CatalogComponent designPattern=new Course("java电商二期课程",77);
javaCourseCatalog.add(mallCourse1);
javaCourseCatalog.add(mallCourse2);
javaCourseCatalog.add(designPattern);
//主目录
CatalogComponent mainCourseCatalog=new CourseCatalog("课程主目录",1);
mainCourseCatalog.add(linuxCourse);
mainCourseCatalog.add(windowCouse);
mainCourseCatalog.add(javaCourseCatalog);
//主目录打印
mainCourseCatalog.print();
}
}
运行结果
课程主目录
Course Name:Linux课程 Price:11.0
Course Name:Windows课程 Price:12.0
java课程目录
Course Name:java电商一期课程 Price:55.0
Course Name:java电商二期课程 Price:66.0
Course Name:java电商二期课程 Price:77.0
源码分析
JDK中awt的Container
public class Container extends Component {
public Component add(Component comp) {
addImpl(comp, null, -1);
return comp;
}
JDK中HashMap
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable {
public void putAll(Map<? extends K, ? extends V> m) {
putMapEntries(m, true);
}
JDK中ArrayList
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
public boolean addAll(Collection<? extends E> c) {
Object[] a = c.toArray();
int numNew = a.length;
ensureCapacityInternal(size + numNew); // Increments modCount
System.arraycopy(a, 0, elementData, size, numNew);
size += numNew;
return numNew != 0;
}
Collection是什么呢?List接口继承它
public interface List<E> extends Collection<E> {
Mybatis中SqlNode
public interface SqlNode {
boolean apply(DynamicContext context);
}
sqlNode的实现类
我们使用的很多sql都是sqlNode的实现类,对于Mybatis而言,不同的sql都会解析成同一类型的SQLNode对象,多个SqlNode怎么结合到一块的呢?MixedSqlNode ,是一个核心类,相当于coding中的课程目录
public class MixedSqlNode implements SqlNode {
private final List<SqlNode> contents;
public MixedSqlNode(List<SqlNode> contents) {
this.contents = contents;
}
@Override
public boolean apply(DynamicContext context) {
contents.forEach(node -> node.apply(context));
return true;
}
}
再看一个我们熟悉的Where子句的SqlNode
public class WhereSqlNode extends TrimSqlNode {
private static List<String> prefixList = Arrays.asList("AND ","OR ","AND\n", "OR\n", "AND\r", "OR\r", "AND\t", "OR\t");
public WhereSqlNode(Configuration configuration, SqlNode contents) {
super(configuration, contents, "WHERE", prefixList, null, null);
}
}
看到SqlNode使用组合模式,是非常清晰的看出来的。
UML类图