定义: 允许动态地向一个现有的对象添加新的功能,同时又不改变其结构,相当于对现有的对象进行了一个包装
UML图
装饰模式的四个关键角色
- 组件:作为装饰器类包装的目标类
- 具体组件:实现组件的基础子类
- 装饰器:一个抽象类,其中包含对组件的引用,并且还重写了组件接口方法
- 具体装饰器:继承扩展了装饰器,并重写组件接口方法,同时可以添加附加功能
使用场景
- 快速动态扩展和撤销一个类的功能场景。 比如,有的场景下对 API
接口的安全性要求较高,那么就可以使用装饰模式对传输的字符串数据进行压缩或加密。如果安全性要求不高,则可以不使用。 - 可以通过顺序组合包装的方式来附加扩张功能的场景。
比如,加解密的装饰器外层可以包装压缩解压缩的装饰器,而压缩解压缩装饰器外层又可以包装特殊字符的筛选过滤的装饰器等。 - 不支持继承扩展类的场景。 比如,使用 final 关键字的类,或者系统中存在大量通过继承产生的子类。
为什么使用装饰模式
- 第一个,为了快速动态扩展类功能,降低开发的时间成本
- 第二个,希望通过继承的方式扩展老旧功能
示例代码1
/**
* @Description 组件-定义了组件具备的基本功能
*/
public interface ComponentService {
void excute();
}
/**
* @Description 具体组件
*/
public class BaseComponentServiceImpl implements ComponentService {
@Override
public void excute() {
// 基础功能的实现
}
}
/**
* @Description 装饰器
*/
public class BaseDecorator implements ComponentService {
private ComponentService wrapper;
public BaseDecorator(ComponentService wrapper) {
this.wrapper = wrapper;
}
@Override
public void excute() {
wrapper.excute();
}
}
/**
* @Description 具体装饰器A
*/
public class DecoratorA extends BaseDecorator {
public DecoratorA(ComponentService wrapper) {
super(wrapper);
}
@Override
public void excute() {
super.excute(); // 原本服务执行
// TODO 后续扩展 在方法执行 之前 之后 都可以
}
}
示例代码2
/**
* @Description 文件读取服务
*/
public interface DataLoaderService {
String readFromFile();
void write2File(String data);
}
import lombok.extern.slf4j.Slf4j;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.OutputStream;
import java.io.FileOutputStream;
import java.nio.charset.StandardCharsets;
/**
* @Description 文件读取服务实现
*/
@Slf4j
public class BaseDataLoaderServceImpl implements DataLoaderService {
public String filePath;
public BaseDataLoaderServceImpl(String filePath){
this.filePath = filePath;
}
@Override
public String readFromFile() {
char[] buffer = null;
File file = new File(this.filePath);
try (FileReader reader = new FileReader(file)){
buffer = new char[(int)file.length()];
reader.read(buffer);
}catch (IOException e){
log.error("BaseDataLoaderServceImpl read error.", e);
}
return new String(buffer);
}
@Override
public void write2File(String data) {
File file = new File(this.filePath);
try(OutputStream fos = new FileOutputStream(file)){
fos.write(data.getBytes(StandardCharsets.UTF_8), 0, data.length());
}catch (IOException e){
log.error("BaseDataLoaderServceImpl write error.", e);
}
}
}
/**
* @Description 基础装饰功能
*/
public class DataLoaderDecorator implements DataLoaderService {
private DataLoaderService dataLoaderService;
public DataLoaderDecorator(DataLoaderService dataLoaderService) {
this.dataLoaderService = dataLoaderService;
}
@Override
public String readFromFile() {
return dataLoaderService.readFromFile();
}
@Override
public void write2File(String data) {
dataLoaderService.write2File(data);
}
}
/**
* @Description 加密装饰
*/
public class EncryptDataLoaderDecorator extends DataLoaderDecorator {
public EncryptDataLoaderDecorator(DataLoaderService dataLoaderService) {
super(dataLoaderService);
}
@Override
public String readFromFile() {
return decrypt(super.readFromFile());
}
@Override
public void write2File(String data) {
super.write2File(encrypt(data));
}
private String encrypt(String data){
byte[] res = data.getBytes(StandardCharsets.UTF_8);
for (int idx = 0 , len = res.length; idx < len; idx++){
res[idx] += (byte) 1;
}
return Base64.getEncoder().encodeToString(res);
}
private String decrypt(String data){
byte[] res = Base64.getDecoder().decode(data);
for (int idx = 0, len = res.length ; idx < len; idx++ ){
res[idx] -= (byte)1;
}
return new String(res, StandardCharsets.UTF_8);
}
}
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Base64;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.InflaterInputStream;
/**
* @Description 压缩装饰
*/
public class CompressionDataLoaderDecorator extends DataLoaderDecorator {
public CompressionDataLoaderDecorator(DataLoaderService dataLoaderService) {
super(dataLoaderService);
}
@Override
public void write2File(String data) {
super.write2File(this.compress(data));
}
@Override
public String readFromFile() {
return this.decompress(super.readFromFile());
}
private String compress(String stringData) {
byte[] data = stringData.getBytes();
try {
ByteArrayOutputStream bout = new ByteArrayOutputStream(512);
DeflaterOutputStream dos = new DeflaterOutputStream(bout, new Deflater());
dos.write(data);
dos.close();
bout.close();
return Base64.getEncoder().encodeToString(bout.toByteArray());
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
private String decompress(String stringData) {
byte[] data = Base64.getDecoder().decode(stringData);
try {
InputStream in = new ByteArrayInputStream(data);
InflaterInputStream iin = new InflaterInputStream(in);
ByteArrayOutputStream bout = new ByteArrayOutputStream(512);
int b;
while ((b = iin.read()) != -1) {
bout.write(b);
}
in.close();
iin.close();
bout.close();
return new String(bout.toByteArray());
} catch (IOException e) {
e.printStackTrace();
return null;
}
}
}
/**
* @Description 测试代码
*/
public class Test {
public static void main(String[] args) {
String tesInfo = "hello";
String filePath = "demo2.txt";
/*DataLoaderDecorator compression = new CompressionDataLoaderDecorator(
new BaseDataLoaderServceImpl(filePath));
compression.write2File(tesInfo);
String s = compression.readFromFile();
System.out.println(s);*/
/*DataLoaderDecorator encrypt = new EncryptDataLoaderDecorator(
new BaseDataLoaderServceImpl(filePath));
encrypt.write2File(tesInfo);
System.out.println(encrypt.readFromFile());*/
// 先 压缩 再加密
// TODO 可以通过 不同的 排列组合 不同的顺序 来实现功能
DataLoaderDecorator compressonAndencrypt =
new CompressionDataLoaderDecorator(
new EncryptDataLoaderDecorator(
new BaseDataLoaderServceImpl(filePath)
)
);
compressonAndencrypt.write2File(tesInfo);
System.out.println(compressonAndencrypt.readFromFile());
}
}
示例代码3
import lombok.Data;
/**
* @Description 饮料
*/
@Data
public abstract class Drink {
private String desc;
private float price = 0.0F;
/**
* 计算费用
* @return
*/
public abstract float cost();
}
/**
* @Description 咖啡的共同特点 抽取出来
*/
public class Coffee extends Drink {
@Override
public float cost() {
return super.getPrice();
}
}
/**
* @Description 意大利咖啡
*/
public class EspressoCoffee extends Coffee {
public EspressoCoffee() {
setDesc("意大利咖啡");
setPrice(6.6f);
}
}
public class AmericanoCoffee extends Coffee {
public LongBlack() {
setDesc("美式咖啡");
setPrice(5.5f);
}
}
public class CappuccinoCoffee extends Coffee {
public LongBlack() {
setDesc("卡布奇诺");
setPrice(7.7f);
}
}
/**
* @Description 基础装饰器
*/
public class Decorator extends Drink {
/**
* 被装饰者
*/
private Drink drink;
public Decorator(Drink drink) {
this.drink = drink;
}
@Override
public float cost() {
// super.getPrice() 装饰者的价格 调料的价格
// drink.cost() 被装饰者的价格 咖啡的价格
return super.getPrice() + drink.cost();
}
@Override
public String getDesc() {
// drink.getDesc() 被装饰者的描述
return super.getDesc() + " " + super.getPrice() + " && " + drink.getDesc();
}
}
public class ChocolateDecorator extends Decorator {
public ChocolateDecorator(Drink obj) {
super(obj);
setDesc("巧克力");
setPrice(3.0f);
}
}
public class MilkDecorator extends Decorator {
public MilkDecorator(Drink obj) {
super(obj);
setDesc("牛奶");
setPrice(2.0f);
}
}
public class SoyDecorator extends Decorator {
public SoyDecorator(Drink obj) {
super(obj);
setDesc("豆浆");
setPrice(1.5f);
}
}
/**
* @Description 测试代码
*/
public class CoffeeClient {
public static void main(String[] args) {
Drink drinkOrder = new AmericanoCoffee();
System.out.println("描述: " + drinkOrder.getDesc());
System.out.println("价格: " + drinkOrder.cost());
drinkOrder = new MilkDecorator(drinkOrder);
System.out.println("描述: " + drinkOrder.getDesc());
System.out.println("价格: " + drinkOrder.cost());
drinkOrder = new SoyDecorator(drinkOrder);
System.out.println("描述: " + drinkOrder.getDesc());
System.out.println("价格: " + drinkOrder.cost());
}
}
运行结果:
描述: 美式咖啡
价格: 5.5
描述: 牛奶 2.0 && 美式咖啡
价格: 7.5
描述: 豆浆 1.5 && 牛奶 2.0 && 美式咖啡
价格: 9.0