设计模式-组合模式(结构型)
定义
- 将对象组合成树形结构以表示“部分-整体”的层次结构。
- 组合模式使客户端对单个对象和组合对象保持一致的方式处理。
适用场景
- 希望客户端可以忽略组合对象与单个对象的差异时。
- 处理一个树形结构时。
优点
- 清楚地定义分层次的复杂对象,表示对象的全部或部分层次。
- 让客户端忽略了层次的差异,方便对整个层次结构进行控制。
- 简化客户端代码。
- 符合开闭原则。
缺点
- 限制类型时会较为复杂。
- 使设计变得更加抽象。
代码
相信很多小伙伴都是车迷,都想拥有一辆属于自己的汽车,但是在我们选购汽车的时候,肯定是根据分类来选择的。下面让我们拿选择宝马汽车来作为一个业务例子,展示组合模式的概念。
我们都知道宝马汽车分为国产和进口,又分为1系,3系,5系,7系,每个系列中有不同的车型来供消费者选择。我们就以这个为例,来以组合模式表示出来。
首先我们需要一个目录组件(catalogComponent
)
这个组件来规定一些车系和车型都需要的内容
/**
* 公共内容的抽象类
*/
public abstract class CatalogComponent {
/**
* 增加车型
* @param carModel
*/
public void addCarModel(CatalogComponent carModel) {
throw new UnsupportedOperationException("不支持添加车型");
}
/**
* 获取名称
* @return
*/
public String getName() {
throw new UnsupportedOperationException("不支持获取名称");
}
/**
* 获取价格
* @return
*/
public String getPrice() {
throw new UnsupportedOperationException("不支持获取价格");
}
/**
* 打印内容
* @return
*/
public void print() {
throw new UnsupportedOperationException("不支持添加打印");
}
}
这里有目录结构需要的添加车型方法,也有车型需要的价格方法,其他的是公共内容。
车型目录类(BMWCarCatalog
)
/**
* BMW车型目录
*/
public class BMWCarCatalog extends CatalogComponent {
List<CatalogComponent> bmwCars = new ArrayList<>();
private String name;
//层级
private Integer level;
public BMWCarCatalog(String name, Integer level) {
this.name = name;
this.level = level;
}
@Override
public void addCarModel(CatalogComponent carModel) {
bmwCars.add(carModel);
}
@Override
public String getName() {
return this.name;
}
@Override
public void print() {
/**
* 为了打印目录结构好看做的优化
*/
System.out.println(name);
for (CatalogComponent bmwCar : bmwCars) {
if(level != null) {
for(int i = 0 ; i < level ; i++) {
System.out.print(" ");
}
}
bmwCar.print();
}
}
}
因为是目录,我们只需要 name
和level
两个变量。同时 只需要重写 addCarModel()
,getName()
呵print()
方法。
车型类(BMWCar
)
/**
* 车型类
*/
public class BMWCar extends CatalogComponent {
private String name;
private String price;
//年份
private String year;
//层级
private Integer level;
public BMWCar(String name, String price, String year, Integer level) {
this.name = name;
this.price = price;
this.year = year;
this.level = level;
}
@Override
public String getName() {
return this.name;
}
@Override
public String getPrice() {
return this.price;
}
@Override
public void print() {
if(level != null) {
for(int i = 0 ; i < level ; i++) {
System.out.print(" ");
}
}
System.out.println(year + "年款, 车型名称:" + name + ",价格为:"+ price);
}
}
这里需要所有的变量 包括名称 年限 价格 和层级。同时重写了 getPrice()
,getName()
呵print()
方法。
测试类
简单的组合对应的类已经完成了,那么现在让我们完成这么一个目录的输出
- 宝马汽车
- 国产
- 一系
- 三系
- 五系
- 进口
- 七系
- 国产
代码示例
就是简单的层级封装。
public class CombinationBootStrap {
public static void main(String[] args) {
//创建国产宝马
CatalogComponent madeInChina = new BMWCarCatalog("国产宝马",1);
//创建进口宝马
CatalogComponent importBMW= new BMWCarCatalog("进口宝马",1);
/**
* 创建国产车系
*/
//1系目录
CatalogComponent oneEr = new BMWCarCatalog("1系",2);
//3系目录
CatalogComponent threeEr = new BMWCarCatalog("3系",2);
//5系目录
CatalogComponent fiveEr = new BMWCarCatalog("5系",2);
/**
* 创建进口车系
*/
CatalogComponent sevenEr = new BMWCarCatalog("7系",2);
/**
* 创建车型
*/
//一系
CatalogComponent oneEr1 = new BMWCar("118i 时尚型","19.88万","2019年",3);
CatalogComponent oneEr2 = new BMWCar("120i 领先型M运动套装","24.38万","2018年",3);
oneEr.addCarModel(oneEr1);
oneEr.addCarModel(oneEr2);
//三系
CatalogComponent threeEr1 = new BMWCar("325i M运动套装","31.39万 ","2020年",3);
CatalogComponent threeEr2 = new BMWCar("320Li 时尚型 ","29.68万起","2019年",3);
threeEr.addCarModel(threeEr1);
threeEr.addCarModel(threeEr2);
//五系
CatalogComponent fiveEr1 = new BMWCar("525Li 豪华套装","42.69万 ","2019年",3);
CatalogComponent fiveEr2 = new BMWCar("530Li 领先型 M运动套装 ","46.39万","2019年",3);
fiveEr.addCarModel(fiveEr1);
fiveEr.addCarModel(fiveEr2);
//添加进国产宝马目录
madeInChina.addCarModel(oneEr);
madeInChina.addCarModel(threeEr);
madeInChina.addCarModel(fiveEr);
//七系
CatalogComponent sevenEr1 = new BMWCar("730Li 豪华套装","82.80万 ","2019年",3);
CatalogComponent sevenEr2 = new BMWCar("740Li 尊享型 M运动套装 ","106.80万 ","2019年",3);
sevenEr.addCarModel(sevenEr1);
sevenEr.addCarModel(sevenEr2);
//添加进口车车系目录
importBMW.addCarModel(sevenEr);
/**
* 创建宝马
*/
CatalogComponent bmw = new BMWCarCatalog("宝马汽车",null);
bmw.addCarModel(madeInChina);
bmw.addCarModel(importBMW);
bmw.print();
}
}
查看输出
bmwoutput.jpg其他源码使用
Spring Boot中 内容协商相关的类ContentNegotiationManager
我们只看特点突出的部分代码即可。
public class ContentNegotiationManager implements ContentNegotiationStrategy, MediaTypeFileExtensionResolver {
private final List<ContentNegotiationStrategy> strategies = new ArrayList<>();
//省略部分代码 ......
}
我们可以看到 ContentNegotiationManager
中有一个List<ContentNegotiationStrategy>
变量,这与我们上面的demo是否很像呢。
我们接下来看一下ContentNegotiationStrategy
接口的几个方法即可。
@FunctionalInterface
public interface ContentNegotiationStrategy {
List<MediaType> resolveMediaTypes(NativeWebRequest webRequest)
throws HttpMediaTypeNotAcceptableException;
}
由接口的方法我们可以确定,在ContentNegotiationManager
一定重写了对应的方法,同时在List<ContentNegotiationStrategy>
数据结构中的每一个也重写了不同的内容。我们就举几个类来看一下就够了。
**ContentNegotiationManager
类中的:**
org.springframework.web.accept.ContentNegotiationManager#resolveMediaTypes
@Override
public List<MediaType> resolveMediaTypes(NativeWebRequest request) throws HttpMediaTypeNotAcceptableException {
for (ContentNegotiationStrategy strategy : this.strategies) {
List<MediaType> mediaTypes = strategy.resolveMediaTypes(request);
if (mediaTypes.equals(MEDIA_TYPE_ALL_LIST)) {
continue;
}
return mediaTypes;
}
return MEDIA_TYPE_ALL_LIST;
}
PathExtensionContentNegotiationStrategy
类中的:
org.springframework.web.accept.PathExtensionContentNegotiationStrategy#getMediaTypeForResource
@Nullable
public MediaType getMediaTypeForResource(Resource resource) {
Assert.notNull(resource, "Resource must not be null");
MediaType mediaType = null;
String filename = resource.getFilename();
String extension = StringUtils.getFilenameExtension(filename);
if (extension != null) {
mediaType = lookupMediaType(extension);
}
if (mediaType == null) {
mediaType = MediaTypeFactory.getMediaType(filename).orElse(null);
}
return mediaType;
}
小结
组合模式在Spring中运用的相当多,同时也可以看到这个模式运用的场景相当广泛。