聊一聊装饰者模式
是你,还是你,一切都有你!—— 装饰者模式一、概述
装饰者模式(Decorator Pattern)允许向一个现有的对象扩展新的功能,同时不改变其结构。主要解决直接继承下因功能的不断横向扩展导致子类膨胀的问题,无需考虑子类的维护。
装饰者模式有4种角色:
[*]抽象构件角色(Component):具体构件类和抽象装饰者类的共同父类。
[*]具体构件角色(ConcreteComponent):抽象构件的子类,装饰者类可以给它增加额外的职责。
[*]装饰角色(Decorator):抽象构件的子类,具体装饰类的父类,用于给具体构件增加职责,但在子类中实现。
[*]具体装饰角色(ConcreteDecorator):具体装饰类,定义了一些新的行为,向构件类添加新的特性。
https://img2022.cnblogs.com/blog/1691717/202211/1691717-20221124204844157-1776831076.png
二、入门案例
2.1、类图
https://img2022.cnblogs.com/blog/1691717/202211/1691717-20221124204857950-1861083051.png
2.2、基础类介绍
// 抽象构件角色
public interface Component {
void doSomeThing();
}
// 具体构件角色
public class ConcreteComponent implements Component {
@Override
public void doSomeThing() {
System.out.println("处理业务逻辑");
}
}
// 装饰者类
public abstract class Decorator implements Component {
private Component component;
public Decorator(Component component) {
this.component = component;
}
@Override
public void doSomeThing() {
// 调用处理业务逻辑
component.doSomeThing();
}
}
// 具体装饰类
public class ConcreteDecorator extends Decorator {
public ConcreteDecorator(Component component) {
super(component);
}
@Override
public void doSomeThing() {
System.out.println("业务逻辑功能扩展");
super.doSomeThing();
}
}当然,如果需要扩展更多功能的话,可以再定义其他的ConcreteDecorator类,实现其他的扩展功能。
如果只有一个ConcreteDecorator类,那么就没有必要建立一个单独的Decorator类,而可以把Decorator和ConcreteDecorator的责任合并成一个类。
三、应用场景
如风之前在一家保险公司干过一段时间。其中保险业务员也会在自家产品注册账号,进行推销。不过在这之前,他们需要经过培训,导入一张展业资格证书。然后再去推销保险产品供用户下单,自己则通过推销产生的业绩,参与分润,拿对应的佣金。
对于上面导证书这个场景,实际上是会根据不同的保险产品,导入不同的证书的。并且证书的类型也不同,对应的解析、校验、执行的业务场景都是不同的。如何去实现呢?当然if-else确实也是一种不错的选择。下面放一段伪代码
/**
* @author 往事如风
* @version 1.0
* @date 2022/11/17 11:32
* @description
*/
@RestController
@RequestMapping("/certificate")
public class CertificateController {
@Resource
private CommonCertificateService certificateService;
@PostMapping("/import")
public Result<Integer> importFile(@RequestParam MultipartFile file, @RequestParam String productCode) {
return Result.success(certificateService.importCertificate(file, productCode));
}
}
/**
* @author 往事如风
* @version 1.0
* @date 2022/11/17 13:25
* @description
*/
@Service
public class CommonCertificateService {
public Integer importCertificate(MultipartFile file, String productCode) {
// 1、参数非空校验
// 2、通过file后缀判断file类型,支持excel和pdf
// 3、解析file文件,获取数据,统一封装到定义的CertificatePojo类中
// 4、根据产品类型判断导入之前的业务逻辑
if (productCode.equals(DecorateConstants.PRODUCT_A)) {
// 重新计算业绩逻辑
// 重新算业绩类型逻辑
// 一坨坨代码去实现....
}
else if (productCode.equals(DecorateConstants.PRODUCT_B)) {
// 导入证书的代理人自己以及上级身份晋升逻辑
// 业绩计算逻辑
// 一坨坨代码去实现...
} else if (productCode.equals(DecorateConstants.PRODUCT_C)) {
// c产品下的业务逻辑
// 一坨坨代码去实现...
} else {
// 默认的处理逻辑
// 一坨坨代码去实现...
}
// 5、证书数据保存
// 6、代理人信息保存
// 7、相关流水数据保存
// 返回代理人id
Integer agentId = Integer.MAX_VALUE;
return agentId;
}
}从上面的伪代码看到,所有的业务逻辑是在一起处理的,通过productCode去处理对应产品的相关逻辑。这么一看,好像也没毛病,但是还是被技术大佬给否决了。好吧,如风决定重写。运用装饰者模式,重新处理下了下这段代码。
1、一切再从注解出发,自定义Decorate注解,这里定义2个属性,scene和type
[*]scene:标记具体的业务场景
[*]type:表示在该种业务场景下,定义一种具体的装饰器类
/**
* @author 往事如风
* @version 1.0
* @date 2022/11/8 17:44
* @description
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Service
public @interface Decorate {
/**
* 具体的业务场景
* @return
*/
String scene();
/**
* 类型:不同业务场景下,不同的装饰器类型
* @return
*/
String type();
}2、抽象构件接口,BaseHandler,这个是必须滴
/**
* @author 往事如风
* @version 1.0
* @date 2022/11/8 17:07
* @description 抽象处理接口
*/
public interface BaseHandler<T, R> {
/**
* 统一的处理方法
* @param t
* @return
*/
R handle(T t);
}3、抽象装饰器类,AbstractHandler,持有一个被装饰类的引用,这个引用具体在运行时被指定
/**
* @author 往事如风
* @version 1.0
* @date 2022-11-13 22:10:05
* @desc 抽象父类
*/
public abstract class AbstractHandler<T, R> implements BaseHandler<T, R> {
protected BaseHandler service;
public void setService(BaseHandler service) {
this.service = service;
}
}4、具体的装饰器类AProductServiceDecorate,主要负责处理“导师证书”这个业务场景下,A产品相关的导入逻辑,并且标记了自定义注解Decorate,表示该类是装饰器类。主要负责对A产品证书导入之前逻辑的增强,我们这里称之为“装饰”。
/**
* @author 往事如风
* @version 1.0
* @date 2022-11-13 23:11:16
* @desc
*/
@Decorate(scene = SceneConstants.CERTIFICATE_IMPORT, type = DecorateConstants.PRODUCT_A)
public class AProductServiceDecorate extends AbstractHandler<MultipartFile, Integer> {
/**
* 重写父类处理数据方法
* @param file
* @return
*/
@Override
public Integer handle(MultipartFile file) {
// 解析
CertificatePojo data = parseData(file);
// 校验
check(data);
// 业绩计算
calAchievement(data.getMobile());
return (Integer) service.handle(data);
}
public CertificatePojo parseData(MultipartFile file) {
// file,证书解析
System.out.println("A产品的证书解析......");
CertificatePojo certificatePojo = new CertificatePojo();
certificatePojo.setMobile("12323");
certificatePojo.setName("张三");
certificatePojo.setMemberNo("req_343242ds");
certificatePojo.setEffectDate("2022-10-31:20:20:10");
return certificatePojo;
}
/**
* 证书数据校验
* @param data
*/
public void check(CertificatePojo data) {
// 数据规范和重复性校验
// .....
System.out.println("A证书数据校验......");
}
/**
* 计算业绩信息
*/
private void calAchievement(String mobile) {
System.out.println("查询用户信息, 手机号:" + mobile);
System.out.println("重新计算业绩...");
}
}当然,还是其他装饰类,BProductServiceDecorate,CProductServiceDecorate等等,负责装饰其他产品,这里就不举例了。
5、当然还有管理装饰器类的装饰器类管理器DecorateManager,内部维护一个map,负责存放具体的装饰器类
/**
* @author 往事如风
* @version 1.0
* @date 2022/11/15 17:18
* @description 装饰管理器
*/
public class DecorateManager {
/**
* 用于存放装饰器类
*/
private Map<String, AbstractHandler> decorateHandleMap = new HashMap<>();
/**
* 将具体装饰器类放在map中
*
* @param handlerList
*/
public void setDecorateHandler(List<AbstractHandler> handlerList) {
for (AbstractHandler h : handlerList) {
Decorate annotation = AnnotationUtils.findAnnotation(h.getClass(), Decorate.class);
decorateHandleMap.put(createKey(annotation.scene(), annotation.type()), h);
}
}
/**
* 返回具体的装饰器类
*
* @param type
* @return
*/
public AbstractHandler selectHandler(String scene, String type) {
String key = createKey(scene, type);
return decorateHandleMap.get(key);
}
/**
* 拼接map的key
* @param scene
* @param type
* @return
*/
private String createKey(String scene, String type) {
return StrUtil.builder().append(scene).append(":").append(type).toString();
}
}6、用了springboot,当然需要将这个管理器交给spring的bean容器去管理,需要创建一个配置类DecorateAutoConfiguration
/**
* @author 往事如风
* @version 1.0
* @date 2022-11-12 19:22:41
* @desc
*/
@Configuration
public class DecorateAutoConfiguration {
@Bean
public DecorateManager handleDecorate(List<AbstractHandler> handlers) {
DecorateManager manager = new DecorateManager();
manager.setDecorateHandler(handlers);
return manager;
}
}7、被装饰的service类,CertificateService,只需要关注自己的核心逻辑就可以
/**
* @author 往事如风
* @version 1.0
* @date 2022/11/8 17:10
* @description 执行证书导入的service
*/
@Service
public class CertificateService implements BaseHandler<CertificatePojo, Integer> {
/**
* 处理导入证书的核心逻辑service
* @param certificate
* @return
*/
@Override
public Integer handle(CertificatePojo certificate) {
System.out.println("核心业务,证书数据:" + JSONUtil.toJsonStr(certificate));
// 1、证书数据保存
// 2、代理人信息保存
// 3、相关流水数据保存
// 其他的一些列核心操作
Integer agentId = Integer.MAX_VALUE;
// 返回代理人id
return agentId;
}
}8、在原来的controller中,注入管理器类DecorateManager去调用,以及service,也就是被装饰的类。首先拿到装饰器,然后再通过setService方法,传入被装饰的service。也就是具体装饰什么类,需要在运行时才确定。
/**
* @author 往事如风
* @version 1.0
* @date 2022-11-13 23:30:37
* @desc
*/
@RestController
public class WebController {
@Resource
private DecorateManager decorateManager;
@Resource
private CertificateService certificateService;
@PostMapping("/import")
public Result importFile(@RequestParam MultipartFile file, @RequestParam String productCode) {
AbstractHandler handler = decorateManager.selectHandler(SceneConstants.CERTIFICATE_IMPORT, productCode);
if (Objects.isNull(handler)) {
return Result.fail();
}
handler.setService(certificateService);
return Result.success(handler.handle(file));
}
}下面模拟下代理人导入证书的流程,当选择A产品,productCode传A过来,后端的处理流程。
[*]对于A产品下,证书的解析,A产品传的是excel
[*]然后数据校验,这个产品下,特有的数据校验
[*]最后是核心的业绩重算,只有A产品才会有这个逻辑
https://img2022.cnblogs.com/blog/1691717/202211/1691717-20221124204932441-109801588.png
当选择B产品,productCode传A过来,后端的处理流程。
[*]对于B产品下,证书的解析,A产品传的是pdf
[*]然后数据校验,跟A产品也不同,多了xxx步骤
[*]核心是代理人的晋升处理,这部分是B产品独有的
https://img2022.cnblogs.com/blog/1691717/202211/1691717-20221124204943798-621897257.png
最后说一句,既然都用springboot了,这块可以写一个starter,做一个公用的装饰器模式。如果哪个服务需要用到,依赖这个装饰器的starter,然后标记Decorate注解,定义对应的scene和type属性,就可以直接使用了。
四、源码中运用
4.1、JDK源码中的运用
来看下IO流中,InputStream、FilterInputStream、FileInputStream、BufferedInputStream的一段代码
public abstract class InputStream implements Closeable { public abstract int read() throws IOException; public int read(byte b[], int off, int len) throws IOException { if (b == null) { throw new NullPointerException(); } else if (off < 0 || len < 0 || len > b.length - off) { throw new IndexOutOfBoundsException(); } else if (len == 0) { return 0; } int c = read(); if (c == -1) { return -1; } b = (byte)c; int i = 1; try { for (; i < len ; i++) { c = read(); if (c == -1) { break; } b = (byte)c; } } catch (IOException ee) { } return i; }}//--------------------------public class FilterInputStream extends InputStream { protected FilterInputStream(InputStream in) { this.in = in; } public int read() throws IOException { return in.read(); }}//--------------------------public class BufferedInputStream extends FilterInputStream { public BufferedInputStream(InputStream in, int size) { super(in); if (size
页:
[1]