• 中文
    • English
  • 注册
  • 查看作者
  • 聊一聊装饰者模式

    一、概述

    装饰者模式(Decorator Pattern)允许向一个现有的对象扩展新的功能,同时不改变其结构。主要解决直接继承下因功能的不断横向扩展导致 子类膨胀 的问题,无需考虑子类的维护。

    装饰者模式有4种角色:

    1. 抽象构件角色(Component):具体构件类和抽象装饰者类的共同父类。

    2. 具体构件角色(ConcreteComponent):抽象构件的子类,装饰者类可以给它增加额外的职责。

    3. 装饰角色(Decorator):抽象构件的子类,具体装饰类的父类,用于给具体构件增加职责,但在子类中实现。

    4. 具体装饰角色(ConcreteDecorator):具体装饰类,定义了一些新的行为,向构件类添加新的特性。

    聊一聊装饰者模式

    二、入门案例

    2.1、类图

    聊一聊装饰者模式

    2.2、基础类介绍

    当然,如果需要扩展更多功能的话,可以再定义其他的ConcreteDecorator类,实现其他的扩展功能。如果只有一个ConcreteDecorator类,那么就没有必要建立一个单独的Decorator类,而可以把Decorator和ConcreteDecorator的责任合并成一个类。

    三、应用场景

    如风之前在一家保险公司干过一段时间。其中保险业务员也会在自家产品注册账号,进行推销。不过在这之前,他们需要经过培训,导入一张展业资格证书。然后再去推销保险产品供用户下单,自己则通过推销产生的业绩,参与分润,拿对应的佣金。

    对于上面导证书这个场景,实际上是会根据不同的保险产品,导入不同的证书的。并且证书的类型也不同,对应的解析、校验、执行的业务场景都是不同的。如何去实现呢?当然if-else确实也是一种不错的选择。下面放一段伪代码

    从上面的伪代码看到,所有的业务逻辑是在一起处理的,通过productCode去处理对应产品的相关逻辑。这么一看,好像也没毛病,但是还是被技术大佬给否决了。好吧,如风决定重写。运用装饰者模式,重新处理下了下这段代码。1、一切再从注解出发,自定义 注解,这里定义2个属性,scene和type

    • scene:标记具体的业务场景

    • type:表示在该种业务场景下,定义一种具体的装饰器类

    2、抽象构件接口, ,这个是必须滴

    3、抽象装饰器类, ,持有一个被装饰类的引用,这个引用具体在运行时被指定

    4、具体的装饰器类 ,主要负责处理“导师证书”这个业务场景下,A产品相关的导入逻辑,并且标记了自定义注解 ,表示该类是装饰器类。主要负责对A产品证书导入之前逻辑的增强,我们这里称之为“装饰”。

    当然,还是其他装饰类, , 等等,负责装饰其他产品,这里就不举例了。5、当然还有管理装饰器类的装饰器类管理器DecorateManager,内部维护一个map,负责存放具体的装饰器类

    6、用了springboot,当然需要将这个管理器交给spring的bean容器去管理,需要创建一个配置类

    7、被装饰的service类, ,只需要关注自己的核心逻辑就可以

    8、在原来的controller中,注入管理器类 去调用,以及service,也就是被装饰的类。首先拿到装饰器,然后再通过setService方法,传入被装饰的service。也就是具体装饰什么类,需要在运行时才确定。

    下面模拟下代理人导入证书的流程,当选择A产品,productCode传A过来,后端的处理流程。

    • 对于A产品下,证书的解析,A产品传的是excel

    • 然后数据校验,这个产品下,特有的数据校验

    • 最后是核心的业绩重算,只有A产品才会有这个逻辑

    聊一聊装饰者模式

    当选择B产品,productCode传A过来,后端的处理流程。

    • 对于B产品下,证书的解析,A产品传的是pdf

    • 然后数据校验,跟A产品也不同,多了xxx步骤

    • 核心是代理人的晋升处理,这部分是B产品独有的

    聊一聊装饰者模式

    最后说一句,既然都用springboot了,这块可以写一个starter,做一个公用的装饰器模式。如果哪个服务需要用到,依赖这个装饰器的starter,然后标记 注解,定义对应的scene和type属性,就可以直接使用了。

    四、源码中运用

    4.1、JDK源码中的运用

    来看下IO流中, 、 、 、 的一段代码

    再来看下这几个类的类图

    聊一聊装饰者模式

    这些类的代码有删改,可以看到 中定义了很多属性,这些数据都是为了可缓冲读取来作准备的,看到其有构造方法会传入一个InputStream的实例。实际编码如下

    这里觉得很眼熟吧,其实已经运用了装饰模式了。

    4.2、mybatis源码中的运用

    在mybatis中,有个接口 ,顾名思义这个接口是个执行器,它底下有许多实现类,如 、 、 等等。类图如下:

    聊一聊装饰者模式

    主要看下 类,看着很眼熟,很标准的装饰器。其中该类中的update是装饰方法,在调用真正update方法之前,会执行刷新本地缓存的方法,对原来的update做增强和扩展。

    再来看下 类,这里有一个update方法,这个是原本的被装饰的update方法。然后再看这个原本的update方法,它调用的doUpdate方法是个抽象方法,用protected修饰。咦,这不就是模板方法么,关于模板方法模式,这里就不展开赘述了。

    五、总结

    优点

    1. 通过组合而非继承的方式,动态地扩展一个对象的功能,在运行时可以选择不同的装饰器从而实现不同的功能。

    2. 有效的避免了使用继承的方式扩展对象功能而带来的灵活性差、子类无限制扩张的问题。

    3. 具体组件类与具体装饰类可以独立变化,用户可以根据需要新增具体组件类跟装饰类,在使用时在对其进行组合,原有代码无须改变,符合”开闭原则”。

    缺点

    1. 这种比继承更加灵活机动的特性,也同时意味着更加多的复杂性。

    2. 装饰模式会导致设计中出现许多小类 (I/O 类中就是这样),如果过度使用,会使程序变得很复杂。

    3. 装饰模式是针对抽象组件(Component)类型编程。但是,如果你要针对具体组件编程时,就应该重新思考你的应用架构,以及装饰者是否合适。

    六、参考源码

  • 0
  • 0
  • 0
  • 21
  • 请登录之后再进行评论

    登录
  • 任务
  • 实时动态
  • 发布
  • 单栏布局 侧栏位置: