设计模式-组合模式(结构型)

定义

  • 将对象组合成树形结构以表示“部分-整体”的层次结构。
  • 组合模式使客户端对单个对象和组合对象保持一致的方式处理。

适用场景

  • 希望客户端可以忽略组合对象与单个对象的差异时。
  • 处理一个树形结构时。

优点

  • 清楚地定义分层次的复杂对象,表示对象的全部或部分层次。
  • 让客户端忽略了层次的差异,方便对整个层次结构进行控制。
  • 简化客户端代码。
  • 符合开闭原则。

缺点

  • 限制类型时会较为复杂。
  • 使设计变得更加抽象。

代码

相信很多小伙伴都是车迷,都想拥有一辆属于自己的汽车,但是在我们选购汽车的时候,肯定是根据分类来选择的。下面让我们拿选择宝马汽车来作为一个业务例子,展示组合模式的概念。

我们都知道宝马汽车分为国产和进口,又分为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 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();
        }
    }
}

因为是目录,我们只需要 namelevel两个变量。同时 只需要重写 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 strategies = new ArrayList<>();
   //省略部分代码 ......
}

我们可以看到 ContentNegotiationManager中有一个List 变量,这与我们上面的demo是否很像呢。

我们接下来看一下ContentNegotiationStrategy接口的几个方法即可。

@FunctionalInterface
public interface ContentNegotiationStrategy {
   List resolveMediaTypes(NativeWebRequest webRequest)
         throws HttpMediaTypeNotAcceptableException;

}

由接口的方法我们可以确定,在ContentNegotiationManager一定重写了对应的方法,同时在List数据结构中的每一个也重写了不同的内容。我们就举几个类来看一下就够了。

**ContentNegotiationManager类中的:**

org.springframework.web.accept.ContentNegotiationManager#resolveMediaTypes

@Override
public List resolveMediaTypes(NativeWebRequest request) throws HttpMediaTypeNotAcceptableException {
   for (ContentNegotiationStrategy strategy : this.strategies) {
      List 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中运用的相当多,同时也可以看到这个模式运用的场景相当广泛。

你可能感兴趣的:(设计模式-组合模式(结构型))