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

定义

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

适用场景

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

优点

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

缺点

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

代码

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

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

因为是目录,我们只需要 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<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中运用的相当多,同时也可以看到这个模式运用的场景相当广泛。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,864评论 6 494
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,175评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,401评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,170评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,276评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,364评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,401评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,179评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,604评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,902评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,070评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,751评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,380评论 3 319
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,077评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,312评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,924评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,957评论 2 351

推荐阅读更多精彩内容

  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,094评论 1 32
  • 创建型模式 工厂模式 工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种类型的设...
    liuyang7519阅读 324评论 0 2
  • 创建型模式 工厂模式 工厂模式(Factory Pattern)是 Java 中最常用的设计模式之一。这种类型的设...
    隔墙送来秋千影阅读 2,665评论 0 11
  • 【学习难度:★★★☆☆,使用频率:★★★★☆】直接出处:组合模式梳理和学习:https://github.com/...
    BruceOuyang阅读 992评论 0 1
  • 家有女儿初成长。 我和老公从来就没有那种望子成龙,望女成凤的思想,我家的孩子也从来没有上过培优班。 小雪儿的学习在...
    月落西窗竹阅读 299评论 18 23