装饰器模式(Decorator)——深入理解与实战应用
本文为原创博文,转载请注明出处,侵权必究!
1、初识装饰器模式
装饰器模式,顾名思义,就是对已经存在的某些类进行装饰,以此来扩展一些功能。其结构图如下:
- Component为统一接口,也是装饰类和被装饰类的基本类型。
- ConcreteComponent为具体实现类,也是被装饰类,他本身是个具有一些功能的完整的类。
- Decorator是装饰类,实现了Component接口的同时还在内部维护了一个ConcreteComponent的实例,并可以通过构造函数初始化。而Decorator本身,通常采用默认实现,他的存在仅仅是一个声明:我要生产出一些用于装饰的子类了。而其子类才是赋有具体装饰效果的装饰产品类。
- ConcreteDecorator是具体的装饰产品类,每一种装饰产品都具有特定的装饰效果。可以通过构造器声明装饰哪种类型的ConcreteComponent,从而对其进行装饰。
2、最简单的代码实现装饰器模式
//基础接口
public interface Component { public void biu();
}
//具体实现类
public class ConcretComponent implements Component { public void biu() { System.out.println("biubiubiu");
}
}
//装饰类
public class Decorator implements Component { public Component component; public Decorator(Component component) { this.component = component;
} public void biu() { this.component.biu();
}
}
//具体装饰类
public class ConcreteDecorator extends Decorator { public ConcreteDecorator(Component component) { super(component);
} public void biu() { System.out.println("ready?go!");
this.component.biu();
}
}
这样一个基本的装饰器体系就出来了,当我们想让Component在打印之前都有一个ready?go!的提示时,就可以使用ConcreteDecorator类了。具体方式如下:
//使用装饰器
Component component = new ConcreteDecorator(new ConcretComponent());
component.biu(); //console:
ready?go!
biubiubiu
3、为何使用装饰器模式
一个设计模式的出现一定有他特殊的价值。仅仅看见上面的结构图你可能会想,为何要兜这么一圈来实现?仅仅是想要多一行输出,我直接继承ConcretComponent,或者直接在另一个Component的实现类中实现不是一样吗?
首先,装饰器的价值在于装饰,他并不影响被装饰类本身的核心功能。在一个继承的体系中,子类通常是互斥的。比如一辆车,品牌只能要么是奥迪、要么是宝马,不可能同时属于奥迪和宝马,而品牌也是一辆车本身的重要属性特征。但当你想要给汽车喷漆,换坐垫,或者更换音响时,这些功能是互相可能兼容的,并且他们的存在不会影响车的核心属性:那就是他是一辆什么车。这时你就可以定义一个装饰器:喷了漆的车。不管他装饰的车是宝马还是奥迪,他的喷漆效果都可以实现。
再回到这个例子中,我们看到的仅仅是一个ConcreteComponent类。在复杂的大型项目中,同一级下的兄弟类通常有很多。当你有五个甚至十个ConcreteComponent时,再想要为每个类都加上“ready?go!”的效果,就要写出五个子类了。毫无疑问这是不合理的。装饰器模式在不影响各个ConcreteComponent核心价值的同时,添加了他特有的装饰效果,具备非常好的通用性,这也是他存在的最大价值。
4、实战中使用装饰器模式
写这篇博客的初衷也是恰好在工作中使用到了这个模式,觉得非常好用。需求大致是这样:采用sls服务监控项目日志,以Json的格式解析,所以需要将项目中的日志封装成json格式再打印。现有的日志体系采用了log4j + slf4j框架搭建而成。调用起来是这样的:
private static final Logger logger = LoggerFactory.getLogger(Component.class);
logger.error(string);
这样打印出来的是毫无规范的一行行字符串。在考虑将其转换成json格式时,我采用了装饰器模式。目前有的是统一接口Logger和其具体实现类,我要加的就是一个装饰类和真正封装成Json格式的装饰产品类。具体实现代码如下:
/**
* logger decorator for other extension
* this class have no specific implementation
* just for a decorator definition
* @author jzb
*
*/
public class DecoratorLogger implements Logger {
public Logger logger; public DecoratorLogger(Logger logger) { this.logger = logger;
}
@Override
public void error(String str) {} @Override
public void info(String str) {} //省略其他默认实现
}
/**
* json logger for formatted output
* @author jzb
*
*/
public class JsonLogger extends DecoratorLogger {
public JsonLogger(Logger logger) { super(logger);
} @Override
public void info(String msg) { JSONObject result = composeBasicJsonResult();
result.put("MESSAGE", msg);
logger.info(result.toString());
} @Override
public void error(String msg) { JSONObject result = composeBasicJsonResult();
result.put("MESSAGE", msg);
logger.error(result.toString());
} public void error(Exception e) { JSONObject result = composeBasicJsonResult();
result.put("EXCEPTION", e.getClass().getName());
String exceptionStackTrace = ExceptionUtils.getStackTrace(e);
result.put("STACKTRACE", exceptionStackTrace);
logger.error(result.toString());
} public static class JsonLoggerFactory { @SuppressWarnings("rawtypes")
public static JsonLogger getLogger(Class clazz) { Logger logger = LoggerFactory.getLogger(clazz);
return new JsonLogger(logger);
}
} private JSONObject composeBasicJsonResult() {
//拼装了一些运行时信息
}
}
可以看到,在JsonLogger中,对于Logger的各种接口,我都用JsonObject对象进行一层封装。在打印的时候,最终还是调用原生接口logger.error(string),只是这个string参数已经被我们装饰过了。如果有额外的需求,我们也可以再写一个函数去实现。比如error(Exception e),只传入一个异常对象,这样在调用时就非常方便了。
另外,为了在新老交替的过程中尽量不改变太多的代码和使用方式。我又在JsonLogger中加入了一个内部的工厂类JsonLoggerFactory(这个类转移到DecoratorLogger中可能更好一些),他包含一个静态方法,用于提供对应的JsonLogger实例。最终在新的日志体系中,使用方式如下:
private static final Logger logger = JsonLoggerFactory.getLogger(Component.class);
logger.error(string);
他唯一与原先不同的地方,就是LoggerFactory -> JsonLoggerFactory,这样的实现,也会被更快更方便的被其他开发者接受和习惯。

装饰器模式(Decorator)——深入理解与实战应用的更多相关文章
- 设计模式(八)装饰器模式Decorator(结构型)
设计模式(八)装饰器模式Decorator(结构型) 1. 概述 若你从事过面向对象开发,实现给一个类或对象增加行为,使用继承机制,这是所有面向对象语言的一个基本特性.如果已经存在的一个类缺少某些方法 ...
- python 设计模式之装饰器模式 Decorator Pattern
#写在前面 已经有一个礼拜多没写博客了,因为沉醉在了<妙味>这部小说里,里面讲的是一个厨师苏秒的故事.现实中大部分人不会有她的天分.我喜欢她的性格:总是想着去解决问题,好像从来没有怨天尤人 ...
- 【PHP设计模式 09_ZhuangShiQi.php】装饰器模式 (decorator)
<?php /** * [装饰器模式 (decorator)] * 有时候发布一篇文章需要经过很多人手,层层处理 */ header("Content-type: text/html; ...
- 装饰器模式-Decorator(Java实现)
装饰器模式-Decorator(Java实现) 装饰器模式允许向一个现有的对象添加新的功能, 同时又不改变其结构. 其中 "现有对象"在本文中是StringDisplay类. 添加 ...
- 用最简单的例子理解装饰器模式(Decorator Pattern)
假设有一个公司要做产品套餐,即把不同的产品组合在一起,不同的组合对应不同的价格.最终呈现出来的效果是:把产品组合的所有元素呈现出来,并显示该组合的价格. 每个产品都有名称和价格,首先设计一个关于产品的 ...
- 装饰器模式 Decorator 结构型 设计模式 (十)
引子 现实世界的装饰器模式 大家应该都吃过手抓饼,本文装饰器模式以手抓饼为模型展开简介 "老板,来一个手抓饼, 加个培根, 加个鸡蛋,多少钱?" 这句话会不 ...
- 说说设计模式~装饰器模式(Decorator)~多功能消息组件的实现
返回目录 为何要设计多功能消息组件 之前写过一篇装饰器模式的文章,感觉不够深入,这次的例子是实现项目中遇到的,所以把它拿出来,再写写,之前也写过消息组件的文章,主要采用了策略模式实现的,即每个项目可以 ...
- 装饰器模式(Decorator)
转自http://blog.csdn.net/hust_is_lcd/article/details/7884320 1.认识装饰器模式 装饰模式能够实现动态的为对象添加功能,是从一个对象外部来给对象 ...
- 设计模式入门之装饰器模式Decorator
//装饰模式定义:动态地给一个对象加入一些额外的职责. //就添加功能来说.装饰模式比生成子类更为灵活 //这也提现了面向对象设计中的一条基本原则,即:尽量使用对象组合,而不是对象继承 //Compo ...
随机推荐
- Oracle 字符集设置
因为装Linux系统时选择的是英文版,所以当需要测试中文数据库时会出现乱码,这里记录一下我修改字符集的操作. 一些命令: 1.查看sqlplus客户编码: $ echo $NLS_LANG 2.查看 ...
- Ajax基础与登入
Ajax 是 Asynchronous JavaScript and XML的缩写. Ajax的优点: 优点:减轻服务器的负担,按需取数据,最大程度的减少冗余请求 局部刷新页面,减少用户心理和实际的等 ...
- 读书笔记 effective c++ Item 31 把文件之间的编译依赖降到最低
1. 牵一发而动全身 现在开始进入你的C++程序,你对你的类实现做了一个很小的改动.注意,不是接口,只是实现:一个私有的stuff.然后你需要rebuild你的程序,计算着这个build应该几秒钟就足 ...
- 基于 Koa.js 平台的 Node.js web 快速开发框架KoaHub.js demo 可安装
KoaHub.js demo KoaHub.js KoaHub.js -- 基于 Koa.js 平台的 Node.js web 快速开发框架.可以直接在项目里使用 ES6/7(Generator Fu ...
- ajax三级联动下拉菜单
ajax写三级联动,先写一个文件类吧,以后用的时候直接调用即可: 来找一张表: 实现: 中国地域的三级联动:省.市.区: 图: 说一下思路: (1)当用户选择省份的时候触发事件,把当前的省份的id通过 ...
- 3522: [Poi2014]Hotel
3522: [Poi2014]Hotel Time Limit: 20 Sec Memory Limit: 128 MBSubmit: 253 Solved: 117[Submit][Status ...
- ehcache 使用笔记
要想使用 java 的本地缓存,可以考虑用 ehcache,或者 guava. guava 更高端一点,可以自动定时刷新.我选择了 ehcache. 在 spring 中是集成了 ehcache 的. ...
- 微信iOS客户端升级内核对自定义分享的影响
上周,业务同学反应,公司的商品详情页,在有的Iphone手机上自定义分享信息失效.在自己手机上一直无法重现,在一个同事的手机上也重现了. 后来看到<微信iOS客户端将升级为WKWebview内核 ...
- quartz任务时间调度入门使用
Quartz 是 OpenSymphony 开源组织在任务调度领域的一个开源项目,完全基于 Java 实现. 作为一个优秀的开源调度框架,Quartz 具有以下特点: 强大的调度功能,例如支持丰富多样 ...
- Telnet 在win7 和 xp中的使用
telnet 默认在开启之后 监听23端口.最近在尝试使用这个服务 虽然很多机器默认是不开启telnet这个服务的 但是 万一能够碰到 也是为了自己以后能够熟练起来咯. telnet有服务器跟客户端之 ...