组合模式属于设计模式中的"结构型模式"。
组合模式,将对象组合成树形结构以表示“部分-整体”的层次结构,组合模式使得用户对单个对象和组合对象的使用具有一致性。
你可以使用它将对象组合成树状结构,并且能像使用独立对象一样使用他们。
组合模式使用递归方式处理对象树中的所有项目。
接口和类的组合方式:
1、组件component接口描述了树中树枝和树叶的共有的操作;
2、树叶leaf是末级节点,不包含下级,主要处理逻辑一般在树叶中;
3、树枝composite是包含其它树叶和树枝的容器,通过组件component接口与下级通信;
组合模式两种实现方式:
1、透明方式
树枝和树叶拥有共同的行为,虽然树叶中的add、remove、display 行为没有意义,可以保持空或者抛出异常。
这样做的好处是叶节点和枝节点对于外界没有区别,它们具备完全一致的接口。
2、安全方式
维护下级节点的相关方法只出现在树枝中。
叶节点无需在实现Add与Remove这样的方法,但是对于客户端来说,必须对叶节点和枝节点进行判定,为客户端的使用带来不便。
应用场景:
对象之间呈现树状结构、金字塔结构。
想表达“部分-整体”层次结构(树形结构)时。
希望用户忽略组合对象与单个对象的不同,用户将统一的使用组合结构中的所有对象。
应用案例:
1、公司组织结构
组织结构:根节点: 董事长,下级:副董事长,总裁,下级:CEO、CTO、CFO
下级:研发部、销售部、市场部、财务部、人力部。。。
整体成树状结构
2、计算订单总价
订单中的项目可能包含几个具体的商品,也可能包括一个套餐,套餐中也可能包含一个具体商品,也可能包括另一个套餐,。。。整体成树状结构分布
3、国家省市直辖市自治区分布
开源框架案例:
Spring中WebMvcConfigurerComposite
class WebMvcConfigurerComposite implements WebMvcConfigurer {
//下级节点集合
private final List<WebMvcConfigurer> delegates = new ArrayList<>();
public void addWebMvcConfigurers(List<WebMvcConfigurer> configurers) {
if (!CollectionUtils.isEmpty(configurers)) {
this.delegates.addAll(configurers);
}
}
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
for (WebMvcConfigurer delegate : this.delegates) {
//递归
delegate.configurePathMatch(configurer);
}
}
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
for (WebMvcConfigurer delegate : this.delegates) {
delegate.configureContentNegotiation(configurer);
}
}
@Override
public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
for (WebMvcConfigurer delegate : this.delegates) {
delegate.configureAsyncSupport(configurer);
}
}
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
for (WebMvcConfigurer delegate : this.delegates) {
delegate.configureDefaultServletHandling(configurer);
}
}
@Override
public void addFormatters(FormatterRegistry registry) {
for (WebMvcConfigurer delegate : this.delegates) {
delegate.addFormatters(registry);
}
}
@Override
public void addInterceptors(InterceptorRegistry registry) {
for (WebMvcConfigurer delegate : this.delegates) {
delegate.addInterceptors(registry);
}
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
for (WebMvcConfigurer delegate : this.delegates) {
delegate.addResourceHandlers(registry);
}
}
@Override
public void addCorsMappings(CorsRegistry registry) {
for (WebMvcConfigurer delegate : this.delegates) {
delegate.addCorsMappings(registry);
}
}
@Override
public void addViewControllers(ViewControllerRegistry registry) {
for (WebMvcConfigurer delegate : this.delegates) {
delegate.addViewControllers(registry);
}
}
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
for (WebMvcConfigurer delegate : this.delegates) {
delegate.configureViewResolvers(registry);
}
}
@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
for (WebMvcConfigurer delegate : this.delegates) {
delegate.addArgumentResolvers(argumentResolvers);
}
}
@Override
public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {
for (WebMvcConfigurer delegate : this.delegates) {
delegate.addReturnValueHandlers(returnValueHandlers);
}
}
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
for (WebMvcConfigurer delegate : this.delegates) {
delegate.configureMessageConverters(converters);
}
}
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
for (WebMvcConfigurer delegate : this.delegates) {
delegate.extendMessageConverters(converters);
}
}
@Override
public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
for (WebMvcConfigurer delegate : this.delegates) {
delegate.configureHandlerExceptionResolvers(exceptionResolvers);
}
}
@Override
public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
for (WebMvcConfigurer delegate : this.delegates) {
delegate.extendHandlerExceptionResolvers(exceptionResolvers);
}
}
@Override
public Validator getValidator() {
Validator selected = null;
for (WebMvcConfigurer configurer : this.delegates) {
Validator validator = configurer.getValidator();
if (validator != null) {
if (selected != null) {
throw new IllegalStateException("No unique Validator found: {" +
selected + ", " + validator + "}");
}
selected = validator;
}
}
return selected;
}
@Override
@Nullable
public MessageCodesResolver getMessageCodesResolver() {
MessageCodesResolver selected = null;
for (WebMvcConfigurer configurer : this.delegates) {
MessageCodesResolver messageCodesResolver = configurer.getMessageCodesResolver();
if (messageCodesResolver != null) {
if (selected != null) {
throw new IllegalStateException("No unique MessageCodesResolver found: {" +
selected + ", " + messageCodesResolver + "}");
}
selected = messageCodesResolver;
}
}
return selected;
}
}
Dubbo中CompositeConfiguration;
public class CompositeConfiguration implements Configuration {
private Logger logger = LoggerFactory.getLogger(CompositeConfiguration.class);
/**
* List holding all the configuration
* 各类配置集合
*/
private List<Configuration> configList = new LinkedList<Configuration>();
public CompositeConfiguration() {
}
public CompositeConfiguration(Configuration... configurations) {
if (configurations != null && configurations.length > 0) {
Arrays.stream(configurations).filter(config -> !configList.contains(config)).forEach(configList::add);
}
}
public void addConfiguration(Configuration configuration) {
if (configList.contains(configuration)) {
return;
}
this.configList.add(configuration);
}
public void addConfigurationFirst(Configuration configuration) {
this.addConfiguration(0, configuration);
}
public void addConfiguration(int pos, Configuration configuration) {
this.configList.add(pos, configuration);
}
@Override
public Object getInternalProperty(String key) {
Configuration firstMatchingConfiguration = null;
for (Configuration config : configList) {
try {
if (config.containsKey(key)) {
firstMatchingConfiguration = config;
break;
}
} catch (Exception e) {
logger.error("Error when trying to get value for key " + key + " from " + config + ", will continue to try the next one.");
}
}
if (firstMatchingConfiguration != null) {
return firstMatchingConfiguration.getProperty(key);
} else {
return null;
}
}
@Override
public boolean containsKey(String key) {
//这里体现了递归思想
return configList.stream().anyMatch(c -> c.containsKey(key));
}
}
mybatis 的MixedSqlNode、ChooseSqlNode;
JDK 的 AWT(Abstract Window Toolkit),使用了组合模式,AWT 中包含了两种组件:容器组件和基本组件;
Spring中CompositeCacheManager;
业务开发应用:
数据迁移系统,一个迁移Job是一个顶级任务,Job下面可以增加要迁移的中介Agency,Agency下面有自己管理的企业Org,这个Job的目标是将中介下面所有企业数据从a数据库迁移到b数据库。
代码原型:
1、抽象一个任务接口
/**
* @Project fighting-core
* @Description 迁移任务
* @Author lvaolin
* @Date 2021/12/29 下午19:49
*/
public interface IMigrationTask {
/**
* 迁移启动
*/
void startMove();
/**
* 迁移耗时统计
* @return
*/
long costTime();
void add(IMigrationTask migrationTask);
void remove(IMigrationTask migrationTask);
}
2、实现一个无差异的树枝任务和树叶任务
/**
* @Project fighting-core
* @Description 迁移节点
* @Author lvaolin
* @Date 2021/12/29 下午19:49
*/
@Data
public class MigrationNode implements IMigrationTask{
private String orgId;
private List<IMigrationTask> migrationNodeList = new ArrayList<>();
private long costTime = 0;
public MigrationNode(){
}
public MigrationNode(String orgId){
this.orgId = orgId;
}
@Override
public void startMove() {
if (migrationNodeList.size()>0) {
//容器节点
for (IMigrationTask migrationNode : migrationNodeList) {
migrationNode.startMove();
}
}else{
costTime = new Random().nextInt(100);
//叶子节点
System.out.println("企业"+orgId+"开始迁移,耗时:"+costTime);
}
}
@Override
public long costTime() {
if (migrationNodeList.size()>0) {
//容器节点
for (IMigrationTask migrationNode : migrationNodeList) {
costTime += migrationNode.costTime();
}
}
return costTime;
}
@Override
public void add(IMigrationTask migrationTask) {
migrationNodeList.add(migrationTask);
}
@Override
public void remove(IMigrationTask migrationTask) {
migrationNodeList.remove(migrationTask);
}
}
上下文模拟
/**
* @Project fighting-core
* @Description 上下文模拟
* @Author lvaolin
* @Date 2021/12/29 下午19:49
*/
public class Main {
public static void main(String[] args) {
//顶级任务
MigrationNode job = new MigrationNode();
//二级任务
MigrationNode agency01 = new MigrationNode();
MigrationNode agency02 = new MigrationNode();
//三级任务
MigrationNode org0101 = new MigrationNode(UUID.randomUUID().toString());
MigrationNode org0102 = new MigrationNode(UUID.randomUUID().toString());
MigrationNode org0103 = new MigrationNode(UUID.randomUUID().toString());
MigrationNode org0201 = new MigrationNode(UUID.randomUUID().toString());
MigrationNode org0202 = new MigrationNode(UUID.randomUUID().toString());
MigrationNode org0203 = new MigrationNode(UUID.randomUUID().toString());
//任务组合
job.add(agency01);
job.add(agency02);
//任务组合
agency01.add(org0101);
agency01.add(org0102);
agency01.add(org0103);
//任务组合
agency02.add(org0201);
agency02.add(org0202);
agency02.add(org0203);
//启动任务
job.startMove();
//统计耗时
System.out.println("总耗时统计:"+job.costTime());
}
}
结果