从源码角度理解Java设计模式——装饰者模式
一、饰器者模式介绍
装饰者模式定义:在不改变原有对象的基础上附加功能,相比生成子类更灵活。
适用场景:动态的给一个对象添加或者撤销功能。
优点:可以不改变原有对象的情况下动态扩展功能,可以使扩展的多个功能按想要的顺序执行,以实现不同效果。
缺点:更多的类,使程序复杂
类型:结构型。
类图:
源码分析中的典型应用
- Java I/O 中的装饰者模式
- Spring Session 中的装饰者模式
- Mybatis 缓存中的装饰者模式
二、给系统添加日志,安全、限流示例
一般系统的安全、日志、限流等业务无关代码可以抽离出来,在Controller前后用切面改造,模板方法模式可以部分解决这个问题:
public abstract class BaseAspect {
Logger logger = LoggerFactory.getLogger(BaseCommand.class);
public void execute(){
//记录日志
logger.debug("..start..");
//过滤跨站脚本攻击
paramXssAspect();
//限制速率
doRateLimit();
doBusiness();
logger.debug("..end..");
}
public abstract void doBusiness();
}
class PlaceOrderAspect extends BaseAspect {
@Override
public void doBusiness() {
//下单操作
}
}
class PayOrderAspect extends BaseAspect {
@Override
public void doBusiness() {
//支付操作
}
}
在父类中已经把”乱七八糟“的非业务代码写好了,只留了一个抽象方法等子类去实现,子类变的很清爽,只需关注业务逻辑就可以了。
这种方式最大的缺陷就是父类会定义一切:要执行那些非业务代码,以什么顺序执行等等,子类只能无条件接受。如果有一个子类,不限制速率,那么它也没有办法把它去掉。
利用装饰者模式,针对上面的问题,可以变的很灵活。
//最高层抽象组件
interface IAspect {
String doHandlerAspect();
}
//基本被装饰类
class AspectImpl implements IAspect{
@Override
public String doHandlerAspect() {
return "裸跑代码.";
}
}
abstract class AbstractDecorator implements IAspect{
//很重要,组合抽象构件到自己的类中
private IAspect aspect;
public AbstractDecorator(IAspect aspect) {//通过IAspect构造自己
this.aspect = aspect;
}
@Override
public String doHandlerAspect() {
return this.aspect.doHandlerAspect();
}
}
附加记录日志,安全,限流功能:
class LoggerAspect extends AbstractDecorator{
public LoggerAspect(IAspect aspect){
super(aspect);
}
@Override
public String doHandlerAspect() {
return super.doHandlerAspect()+"+记录日志.";
}
}
class ParamXssAspect extends AbstractDecorator{
public ParamXssAspect(IAspect aspect){
super(aspect);
}
@Override
public String doHandlerAspect() {
return super.doHandlerAspect()+"+过滤危险字符.";
}
}
class LimitAspect extends AbstractDecorator{
public LimitAspect(IAspect aspect){
super(aspect);
}
@Override
public String doHandlerAspect() {
return super.doHandlerAspect()+"+限流.";
}
}
测试一下:
public class Test {
public static void main(String[] args) {
IAspect aspect = new LimitAspect(new ParamXssAspect(new LoggerAspect(new AspectImpl())));
System.out.println(aspect.doHandlerAspect());
}
}
运行结果:
------
裸跑代码.+记录日志.+过滤危险字符.+限流.
------
通过上面可以看出,装饰者模式可以任意次序组装功能,是不是很灵活?另外,也可以把上述三个功能封装成注解@Log、@ParamXss、@AccessLimit,实现可拔插。如果读者想看注解功能完整实现,可以参考我的这个项目:SpringBoot+JWT+Shiro+MybatisPlus实现Restful快速开发后端脚手架
三、源码中的装饰者模式
3.1、Java IO中是体现最明显的装饰者模式。
它基于字符流(InputStream/OutputStream) 和 字节流(Reader/Writer)作为基类,下面画出InputStream、Reader的部分类图:
这里总结几种常用流的应用场景:
流名称 | 应用场景 |
---|---|
ByteArrayInputStream | 访问数组,把内存中的一个缓冲区作为 InputStream 使用,CPU从缓存区读取数据比从存储介质的速率快10倍以上 |
StringBufferInputStream | 把一个 String 对象作为。InputStream。不建议使用,在转换字符的问题上有缺陷 |
FileInputStream | 访问文件,把一个文件作为 InputStream ,实现对文件的读取操作 |
PipedInputStream | 访问管道,主要在线程中使用,一个线程通过管道输出流发送数据,而另一个线程通过管道输入流读取数据,这样可实现两个线程间的通讯 |
SequenceInputStream | 把多个 InputStream 合并为一个 InputStream . “序列输入流”类允许应用程序把几个输入流连续地合并起来 |
DataInputStream | 特殊流,读各种基本类型数据,如byte、int、String的功能 |
ObjectInputStream | 对象流,读对象的功能 |
PushBackInputStream | 推回输入流,可以把读取进来的某些数据重新回退到输入流的缓冲区之中 |
BufferedInputStream | 缓冲流,增加了缓冲功能 |
3.2、Spring Session中的ServletRequestWrapper(Response也一样)的装饰者模式。
public class ServletRequestWrapper implements ServletRequest {
private ServletRequest request;//组合抽象接口到自己的类中
public ServletRequestWrapper(ServletRequest request) {//可以构造自己
if(request == null) {
throw new IllegalArgumentException("Request cannot be null");
} else {
this.request = request;
}
}
public ServletRequest getRequest() {
return this.request;
}
public void setRequest(ServletRequest request) {
if(request == null) {
throw new IllegalArgumentException("Request cannot be null");
} else {
this.request = request;
}
}
//省略...
}
3.3、Spring Cache中的TransactionAwareCacheDecorator的装饰者模式。
其实从类名就可以看出。
public class TransactionAwareCacheDecorator implements Cache {
private final Cache targetCache;//把Cache组合到自己类中
public TransactionAwareCacheDecorator(Cache targetCache) {//通过Cache构造自己
Assert.notNull(targetCache, "Target Cache must not be null");
this.targetCache = targetCache;
}
public <T> T get(Object key, Class<T> type) {
return this.targetCache.get(key, type);
}
public void put(final Object key, final Object value) {
// 判断是否开启了事务
if (TransactionSynchronizationManager.isSynchronizationActive()) {
// 将操作注册到 afterCommit 阶段
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
public void afterCommit() {
TransactionAwareCacheDecorator.this.targetCache.put(key, value);
}
});
} else {
this.targetCache.put(key, value);
}
}
// ...省略...
}
3.4、Mybatis中的装饰者。
Cache为抽象构件类,PerpetualCache为具体构件类,decorators包下的类为装饰类,这里没有抽象装饰类。
参考:
《码农翻身》刘欣
原文链接:从源码角度理解Java设计模式——装饰者模式
从源码角度理解Java设计模式——装饰者模式的更多相关文章
- JAVA设计模式--装饰器模式
装饰器模式 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构.这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装. 这种模式创建了一个装饰 ...
- 【设计模式】Java设计模式 - 装饰者模式
Java设计模式 - 装饰者模式 不断学习才是王道 继续踏上学习之路,学之分享笔记 总有一天我也能像各位大佬一样 原创作品,更多关注我CSDN: 一个有梦有戏的人 准备将博客园.CSDN一起记录分享自 ...
- Java设计模式——装饰者模式
JAVA 设计模式 装饰者模式 用途 装饰者模式 (Decorator) 动态地给一个对象添加一些额外的职责.就增加功能来说,Decorator 模式相比生成子类更为灵活. 装饰者模式是一种结构式模式 ...
- 【转】java comparator 升序、降序、倒序从源码角度理解
原文链接:https://blog.csdn.net/u013066244/article/details/78997869 环境jdk:1.7+ 前言之前我写过关于comparator的理解,但是都 ...
- 从源码角度学习Java动态代理
前言 最近,看了一下关于RMI(Remote Method Invocation)相关的知识,遇到了一个动态代理的问题,然后就决定探究一下动态代理. 这里先科普一下RMI. RMI 像我们平时写的程序 ...
- 从源码角度理解android动画Interpolator类的使用
做过android动画的人对Interpolator应该不会陌生,这个类主要是用来控制android动画的执行速率,一般情况下,如果我们不设置,动画都不是匀速执行的,系统默认是先加速后减速这样一种动画 ...
- 从JDK源码角度看java并发的公平性
JAVA为简化开发者开发提供了很多并发的工具,包括各种同步器,有了JDK我们只要学会简单使用类API即可.但这并不意味着不需要探索其具体的实现机制,本文从JDK源码角度简单讲讲并发时线程竞争的公平性. ...
- 从JDK源码角度看java并发的原子性如何保证
JDK源码中,在研究AQS框架时,会发现很多地方都使用了CAS操作,在并发实现中CAS操作必须具备原子性,而且是硬件级别的原子性,java被隔离在硬件之上,明显力不从心,这时为了能直接操作操作系统层面 ...
- Java设计模式の装饰者模式
目录 一.问题引入 二.设计原则 三.用装饰者模式解决问题 四.装饰者模式的特点 五.装饰者模式的定义 六.装饰者模式的实现 七.java.io包内的装饰者模式 一.问题引入 咖啡店的类设计: 一个饮 ...
随机推荐
- 华为云服务器基于hadoop2.7.5安装hive-2.3.7
1. Hive简介 The Apache Hive data warehouse software facilitates reading, writing, and managing large d ...
- 初学WebGL引擎-BabylonJS:第4篇-灯光动画与丛林场景
前几章接触的案例都是接近静态的,由这张开始开始接触大量动态的内容,包括 球体灯光,变动的形体,以及一个虚拟的丛林场景 下章我会试着结合1-9案例的内容做出一个demo出来 [playground]-l ...
- cordova 环境配制和创建插件
环境配制 英文网站:http://cordova.apache.org/ 中文网站:http://cordova.axuer.com/ 安装Cordova Cordova的命令行运行在Node.js ...
- VS停止调试,IIS Express也跟着关闭了
问题描述: 我们会时不时地用VS进行调试,当点击停止调试的时候,网站再刷新一下,便会出现网页走丢的现象,然后需要重新打开网站,很是麻烦,令人抓狂.如何解决呢? 首先说下,为啥会产生这种问题? 大致描述 ...
- Arbitrary-Oriented Object Detection with Circular Smooth Label(ECCV2020,旋转目标检测)
论文链接:https://arxiv.org/abs/2003.05597 code:https://github.com/Thinklab-SJTU/CSL_RetinaNet_Tensorflow ...
- HDU—2021-发工资咯(水题,有点贪心的思想)
作为杭电的老师,最盼望的日子就是每月的8号了,因为这一天是发工资的日子,养家糊口就靠它了,呵呵 但是对于学校财务处的工作人员来说,这一天则是很忙碌的一天,财务处的小胡老师最近就在考虑一个问题:如果每 ...
- [BUUOJ记录] [SUCTF 2019]CheckIn
比较经典的一道文件上传题,考察.user.ini控制解析图片方式 打开题目给出了上传功能,源代码里也没有任何提示,看来需要先测试一下过滤 前后依次提交了php,php5,php7,phtml拓展名的文 ...
- 最小生成树MST
定义 在一给定的无向联通带权图\(G = (V, E, W)\)中,\((u, v)\) 代表连接顶点 \(u\) 与顶点 \(v\) 的边,而 \(w(u, v)\) 代表此边的权重,若存在 \(T ...
- 记录call、apply、bind的源码
记录一下call.apply.bind的源码,然后从根本上明白其用法. 都知道call.apply与bind的用法,call(this,...arguments).apply(this,[argume ...
- Spring JPA 查询创建
Spring JPA 查询创建 这是JPA内容的核心部分,可以收藏用作参阅文档. 1. 查询转化和关键字 例:一个JPA查询的转化 public interface UserRepository ex ...