定义 将对象组合成树形结构以表示“部分-整体”的层次结构。作用是使客户端对单个对象和组合对象保持一致的方式处理。组合模式就是将多个对象组合成一个对象(这些对象具有相同的类型,使用它们的父类型作为统一的对象供客户端访问),简化了对多个对象的访问
类型 结构型
使用场景 1 2 1. 希望客户端可以忽略组合对象与单个对象的差异时 2. 处理一个树形结构时
优点 1 2 3 4 ◆清楚地定义了分层次的复杂对象,表示对象的全部或部分层次 ◆让客户端忽略了层次的差异,方便对整个层次结构进行控制 ◆简化客户端代码 ◆符合开闭原则
缺点 1 2 1. 限制类型时会较为复杂,因为它们都具有相同的父类型 2. 使设计变得更加抽象
相关的设计模式 组合模式和访问者模式
可以用访问模式访问组合模式的递归结构
简单需求 课程分为不同的类型,每一种类型对应一个课程目录,课程目录下又有很多的课程,要求打印出课程的结构
组合模式的演练
1 我们可以通过行为方法进行识别组合模式,组合模式是将相同的抽象类类型或者接口类型转为相同的树状结构,使用抽象作为访问的入口。叶子对象(单个对象)和组合好的对象(包含叶子对象的集合)都要继承或实现相同的父类,这样组合模式才能将它们进行统一处理。
统一抽象类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 package com.design.pattern.composite;public abstract class CourseComponet { public void addCatalog (CourseComponet courseComponet) { throw new UnsupportedOperationException("不支持添加课程目录操作" ); } public void removeCatalog (CourseComponet courseComponet) { throw new UnsupportedOperationException("不支持删除课程目录操作" ); } public String getName (CourseComponet courseComponet) { throw new UnsupportedOperationException("不支持获取课程名称操作" ); } public double getPrice (CourseComponet courseComponet) { throw new UnsupportedOperationException("不支持获取课程价格操作" ); } public void print () { throw new UnsupportedOperationException("不支持打印操作" ); } }
课程类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 package com.design.pattern.composite; import lombok.extern.slf4j.Slf4j; /** * Course 课程类 * * @author shunhua * @date 2019-09-24 */ @Slf4j public class Course extends CourseComponet { /** * 课程名 */ private String courseName; /** * 课程价格 */ private double price; public Course(String courseName, double price) { this.courseName = courseName; this.price = price; } @Override public String getName(CourseComponet courseComponet) { return this.courseName; } @Override public double getPrice(CourseComponet courseComponet) { return this.price; } @Override public void print() { log.info("课程名:" + courseName + ", 课程价格:" + price); } }
课程目录类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 package com.design.pattern.composite;import lombok.extern.slf4j.Slf4j;import java.util.ArrayList;import java.util.List;@Slf 4jpublic class CourseCatalog extends CourseComponet { private List<CourseComponet> items = new ArrayList<>(); private String catalogName; public CourseCatalog (String catalogName) { this .catalogName = catalogName; } @Override public void addCatalog (CourseComponet courseComponet) { this .items.add(courseComponet); } @Override public void removeCatalog (CourseComponet courseComponet) { this .items.remove(courseComponet); } @Override public String getName (CourseComponet courseComponet) { return this .catalogName; } @Override public void print () { log.info(catalogName); for (CourseComponet courseComponet : items){ courseComponet.print(); } } }
客户端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package com.design.pattern.composite; import org.junit.Test; /** * Client 课程目录和课程,对客户端来说都是一个类型的对象 * * @author shunhua * @date 2019-09-24 */ public class Client { @Test public void test(){ CourseComponet catalog = new CourseCatalog("课程顶级目录"); CourseComponet linuxCourse = new Course("鸟哥私房菜",80); CourseComponet gitCourse = new Course("Git权威指南",120); CourseComponet javaCatalog = new CourseCatalog("Java课程目录"); CourseComponet spring = new Course("Spring实战",70); CourseComponet mybatis = new Course("MyBatis技术内幕",60); javaCatalog.addCatalog(spring); javaCatalog.addCatalog(mybatis); catalog.addCatalog(linuxCourse); catalog.addCatalog(gitCourse); catalog.addCatalog(javaCatalog); // 打印课程目录以及目录下的课程列表 catalog.print(); } }
组合模式源码解析 jdk源码之HashMap
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public class HashMap <K ,V > extends AbstractMap <K ,V > implements Map <K ,V >, Cloneable , Serializable { public void putAll (Map<? extends K, ? extends V> m) { int numKeysToBeAdded = m.size(); if (numKeysToBeAdded == 0 ) return ; if (table == EMPTY_TABLE) { inflateTable((int ) Math.max(numKeysToBeAdded * loadFactor, threshold)); } if (numKeysToBeAdded > threshold) { int targetCapacity = (int )(numKeysToBeAdded / loadFactor + 1 ); if (targetCapacity > MAXIMUM_CAPACITY) targetCapacity = MAXIMUM_CAPACITY; int newCapacity = table.length; while (newCapacity < targetCapacity) newCapacity <<= 1 ; if (newCapacity > table.length) resize(newCapacity); } for (Map.Entry<? extends K, ? extends V> e : m.entrySet()) put(e.getKey(), e.getValue()); } }
jdk源码之ArrayList
1 2 3 4 5 6 7 8 9 10 11 public boolean addAll (Collection<? extends E> c) { Object[] a = c.toArray(); int numNew = a.length; ensureCapacityInternal(size + numNew); System.arraycopy(a, 0 , elementData, size, numNew); size += numNew; return numNew != 0 ; }
MyBatis源码之SqlNode
1 2 MyBatis的sql语句会被解析成不同的SqlNode类型的对象,这些对象都实现了SqlNode。其中MixedSqlNode是联系不同的SqlNode 的一个核心对象,组合模式可以统一处理的它们。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package org.apache.ibatis.scripting.xmltags;import java.util.List;public class MixedSqlNode implements SqlNode { private final List<SqlNode> contents; public MixedSqlNode (List<SqlNode> contents) { this .contents = contents; } @Override public boolean apply (DynamicContext context) { for (SqlNode sqlNode : contents) { sqlNode.apply(context); } return true ; } }