装饰器模式
1. 装饰器模式定义
装饰器模式(Decorator Pattern),也称为包装模式(Wrapper Pattern)是指在不改变原有对象 的基础之上,将功能附加到对象上,提供了比继承更有弹性的替代方案(扩展原有对象的功能),属于 结构型模式。
原文:Attach additional responsibilities to an object dynamically keeping the same interface. Decorators provide a flexible alternative to subclassing for extending functionality. 解释:动态地给一个对象添加一些额外的职责。就增加功能来说,装饰器模式相比生成子类更为灵活。
2.装饰器模式作用
- 装饰器模式的核心是功能扩展。
- 装饰器模式主要用于透明且动态地扩展类的功能。
其实现原理为:让装饰器实现被包装类(Concrete Component)相同的接口(Component)(使得装饰器与被扩展类类型一致),并在构造函数中传入 该接口(Component)对象,然后就可以在接口需要实现的方法中在被包装类对象的现有功能上添加 新功能了。而且由于装饰器与被包装类属于同一类型(均为 Component),且构造函数的参数为其实 现接口类(Component),因此装饰器模式具备嵌套扩展功能,这样我们就能使用装饰器模式一层一 层的对最底层被包装类进行功能扩展了。
3.装饰器模式使用场景
装饰器模式在我们生活中应用也比较多如给煎饼加鸡蛋;给蛋糕加上一些水果;给房子装修等,为对象扩展一些额外的职责。
装饰器在代码程序中适用于以下场景:
1、用于扩展一个类的功能或给一个类添加附加职责。
2、动态的给一个对象添加功能,这些功能可以再动态的撤销。
3、需要为一批的兄弟类进行改装或加装功能。
4.示例代码
创建一个建煎饼的抽象 Battercake 类:
package com.gupaoedu.vip.pattern.decorator.battercake.v2;
public abstract class Battercake {
protected abstract String getMsg();
protected abstract int price();
}
创建一个基本的煎饼(或者叫基础套餐)BaseBattercake,继承抽象类
package com.gupaoedu.vip.pattern.decorator.battercake.v2;
public class BaseBattercake extends Battercake{
public String getMsg(){
return "煎饼";
}
public int price(){
return 5;
}
}
然后,再创建一个扩展套餐的抽象装饰器 BattercakeDecotator 类:
package com.gupaoedu.vip.pattern.decorator.battercake.v2;
/**
* 煎饼的包装器的抽象,无论怎么包装,本质都是一个煎饼!!!
*/
public abstract class BattercakeDecorator extends Battercake {
//被包装的对象的引用
private Battercake battercake;
//把被包装的对象 以构造方法的方式传递进来
public BattercakeDecorator(Battercake battercake) {
this.battercake = battercake;
}
@Override
protected String getMsg() {
return this.battercake.getMsg();
}
@Override
protected int price() {
return this.battercake.price();
}
}
然后,创建鸡蛋装饰器 EggDecorator 类:
package com.gupaoedu.vip.pattern.decorator.battercake.v2;
/**
* 加鸡蛋的包装器类
*/
public class EggDecorator extends BattercakeDecorator {
public EggDecorator(Battercake battercake) {
super(battercake);
}
@Override
protected String getMsg() {
return super.getMsg() + "+1个鸡蛋";
}
@Override
protected int price() {
return super.price() + 1;
}
}
创建香肠装饰器 SausageDecorator 类:
package com.gupaoedu.vip.pattern.decorator.battercake.v2;
public class SausageDecorator extends BattercakeDecorator {
public SausageDecorator(Battercake battercake) {
super(battercake);
}
@Override
protected String getMsg() {
return super.getMsg() + "+1个香肠";
}
@Override
protected int price() {
return super.price() + 3;
}
protected void doSomething(){
}
}
客户端测试代码:
package com.gupaoedu.vip.pattern.decorator.battercake.v2;
public class BatterCakeTest {
public static void main(String[] args) {
Battercake battercake;
//买一个最基本的煎饼
battercake = new BaseBattercake();
//加一个鸡蛋的煎饼
battercake = new EggDecorator(battercake);
//再加一个鸡蛋
battercake = new EggDecorator(battercake);
//再加一个香肠
battercake = new SausageDecorator(battercake);
System.out.println(battercake.getMsg() + ",总价格:" + battercake.price());
TransactionAwareCacheDecorator cacheDecorator;
HttpHeadResponseDecorator responseDecorator;
}
}
UML类图:
抽象组件(Battercake):可以是一个接口或者抽象类,其充当被装饰类的原始对象,规定了被 装饰对象的行为;
具体组件(BaseBattercake):实现/继承 Battercake的一个具体对象,也即被装饰对象;
抽象装饰器(BattercakeDecorator):通用的装饰 BaseBattercake的装饰器,其内Battercake抽象组件的引用;
其实现一般是一个抽象类,主要是为了让其子类按照其构造形式传入一 个 Battercake抽象组件,这是强制的通用行为(当然,如果系统中装饰逻辑单一,并不需要实现许多装饰器,那么我们可以直接省略该类,而直接实现一个具体装饰器即可);
具体装饰器(EggDecorator):Battercake的具体实现类,理论上,每个 EggDecorator都扩展了 Battercake对象的一种功能;
-
第二个示例
log4j原本的日志打印,在打印异常信息时是一种没有格式的打印,现在要把打印的日志统一成json格式打印,可以通过装饰器模式设计一个装饰类,实现Logger接口,构造方法中把Logger传递进去,再写一个具体的装饰类,完成业务逻辑: -
创建装饰器类 DecoratorLogger
package com.gupaoedu.vip.pattern.decorator.logger;
import org.slf4j.Logger;
import org.slf4j.Marker;
/**
* 创建一个日志的装饰类,持有Logger接口的引用,并且构造参数也是Logger
*/
public class LoggerDecorator implements Logger {
// 被包装的对象的引用
protected Logger logger;
public LoggerDecorator(Logger logger){
this.logger = logger;
}
@Override
public String getName() {
return null;
}
@Override
public boolean isTraceEnabled() {
return false;
}
@Override
public void trace(String msg) {
}
…… // 其他代码太长省略
- 创建具体组件 JasonLogger 类
在 JsonLogger 中,对于 Logger 的各种接口,都用 JsonObject 对象进行一层封装。 在打印的时候,最终还是调用原生接口 logger.error(string),只是这个 string 参数已经被我们装饰过的
package com.gupaoedu.vip.pattern.decorator.logger;
import com.alibaba.fastjson.JSONObject;
import org.slf4j.Logger;
import java.util.Arrays;
/**
* 具体的装饰类,将日志打印封装成为json格式输出
*/
public class JsonLogger extends LoggerDecorator {
public JsonLogger(Logger logger) {
super(logger);
}
@Override
public void info(String msg) {
JSONObject result = newJsonObject();
result.put("message", msg);
logger.info(result.toString());
}
@Override
public void error(String msg) {
JSONObject result = newJsonObject();
result.put("message", msg);
logger.error(result.toString());
}
@Override
public void error(String msg, Throwable t) {
JSONObject result = newJsonObject();
result.put("message", msg);
if (t instanceof Exception) {
Exception ex = (Exception) t;
result.put("exception", ex.getClass().getName());
String trace = Arrays.toString(ex.getStackTrace());
result.put("starckTrace", trace);
}
logger.error(result.toString());
}
private JSONObject newJsonObject() {
return new JSONObject();
}
}
- 通过一个工厂模式,提供对外获取JsonLogger实例的方法:
package com.gupaoedu.vip.pattern.decorator.logger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class JsonLoggerFactory {
public static JsonLogger getLogger(Class clazz){
Logger logger = LoggerFactory.getLogger(clazz);
// 将Logger接口的引用作为构造参数赋值给JsonLogger,返回的是被包装过的logger
return new JsonLogger(logger);
}
}
- 客户端调用
package com.gupaoedu.vip.pattern.decorator.logger;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class LoggerTest {
private static final Logger logger = LoggerFactory.getLogger(LoggerTest.class);
// private static final Logger logger = JsonLoggerFactory.getLogger(LoggerTest.class);
public static void main(String[] args) {
logger.info("打印info日志");
logger.error("打印error日志");
try{
int i = 1/0;
}catch (Exception e){
logger.error("error occured.",e);
}
}
}
测试结果如下,可以看到,日志的打印,全部变成了json格式
15:39:39.062 [main] INFO com.gupaoedu.vip.pattern.decorator.logger.LoggerTest - {"message":"打印info日志"}
15:39:39.064 [main] ERROR com.gupaoedu.vip.pattern.decorator.logger.LoggerTest - {"message":"打印error日志"}
15:39:39.065 [main] ERROR com.gupaoedu.vip.pattern.decorator.logger.LoggerTest - {"exception":"java.lang.ArithmeticException","starckTrace":"[com.gupaoedu.vip.pattern.decorator.logger.LoggerTest.main(LoggerTest.java:12)]","message":"error occured."}
5.装饰器模式的优缺点
优点:
- 1、装饰器是继承的有力补充,比继承灵活,不改变原有对象的情况下动态地给一个对象扩展功能,即 插即用。
- 2、通过使用不同装饰类以及这些装饰类的排列组合,可以实现不同效果。
- 3、装饰器完全遵守开闭原则。
缺点: - 1、会出现更多的代码,更多的类,增加程序复杂性。
- 2、动态装饰时,多层装饰时会更复杂