找回密码
 立即注册
首页 业界区 业界 掌握设计模式--装饰模式

掌握设计模式--装饰模式

庾芷秋 2025-6-6 14:11:24
装饰模式(Decorator Pattern)

装饰模式是一种结构型设计模式,旨在在不改变原有对象结构的情况下动态地为对象添加功能。通过将对象封装到一系列装饰器类中,可以以灵活和透明的方式扩展功能。
如果要扩展功能,装饰模式提供了比继承更有弹性的替代方案,装饰模式强调的是功能的扩展和灵活组合。
装饰模式强调的是扩展对象的功能及扩展功能的组合。比如,对象A,需要扩展X、Y的功能,这些扩展功能按不同的顺序组合可以实现不同的效果,先执行X再执行Y扩展功能或者先执行Y再执行X扩展功能。而继承实现的只是单一的顺序扩展功能,并且继承是单一的。看后面的例子就很好理解了。
结构

通常包含一个核心对象和若干装饰对象,这些装饰对象通过引用同一接口或抽象类实现功能的叠加。
装饰模式包含以下主要角色:

  • Component(组件接口)
    定义核心对象的公共接口,允许动态添加行为。
  • ConcreteComponent(具体组件)
    具体实现了 Component 接口的类,表示被装饰的核心对象。
  • Decorator(装饰器抽象类)
    实现 Component 接口,同时持有一个 Component 的引用,表示对该对象的包装。
  • ConcreteDecorator(具体装饰器)
    在装饰器抽象类的基础上,添加具体的功能。
代码示例

实现对Socket报文的多层加密,加密顺序可随意组合。示例中使用到了国密SM4加密和Base64编码。Socket 报文可以先加密再编码,也可以先编码在加密,跟根据不同组合来实现不同的增强。
类图

1.png

定义核心接口
  1. public interface SocketStream {
  2.     void writeData(String data) throws IOException;
  3.     String readData() throws IOException;
  4.     void close() throws IOException;
  5. }
复制代码
被装饰的核心对象
  1. public class SimpleSocketStream implements SocketStream {
  2.     private Socket socket;
  3.     private OutputStream outputStream;
  4.     private InputStream inputStream;
  5.     public SimpleSocketStream(Socket socket){
  6.         this.socket = socket;
  7.         try {
  8.             this.outputStream = socket.getOutputStream();
  9.             this.inputStream = socket.getInputStream();
  10.         } catch (IOException e) {
  11.             throw new RuntimeException(e);
  12.         }
  13.     }
  14.     @Override
  15.     public void writeData(String data) throws IOException {
  16.         outputStream.write(data.getBytes(StandardCharsets.UTF_8));
  17.         outputStream.flush();
  18.         socket.shutdownOutput();
  19.     }
  20.     @Override
  21.     public String readData() throws IOException {
  22.         // 读取客户端请求并解密
  23.         BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
  24.         String message = reader.readLine();
  25.         return message;
  26.     }
  27.     @Override
  28.     public void close() throws IOException {
  29.         if(inputStream!=null)
  30.             inputStream.close();
  31.         if(outputStream!=null)
  32.             outputStream.close();
  33.         if(socket!=null)
  34.             socket.close();
  35.     }
  36. }
复制代码
抽象装饰器
  1. public abstract class SocketStreamDecorator implements SocketStream{
  2.     private SocketStream socketStream;
  3.     public SocketStreamDecorator(SocketStream socketStream){
  4.         this.socketStream = socketStream;
  5.     }
  6.     @Override
  7.     public void writeData(String data) throws IOException {
  8.         socketStream.writeData(data);
  9.     }
  10.     @Override
  11.     public String readData() throws IOException {
  12.         return socketStream.readData();
  13.     }
  14.     @Override
  15.     public void close() throws IOException {
  16.         socketStream.close();
  17.     }
  18. }
复制代码
具体装饰器1--SM4 加密处理
  1. public class SM4CipherSocketStreamDecorator extends SocketStreamDecorator{
  2.     public SM4CipherSocketStreamDecorator(SocketStream socketStream) {
  3.         super(socketStream);
  4.     }
  5.     @Override
  6.     public void writeData(String data) throws IOException {
  7.         // 加密
  8.         String sm4Encrypt = SM4EncryptUtil.sm4Encrypt(data);
  9.         System.out.println("--SM4加密数据:"+sm4Encrypt);
  10.         super.writeData(sm4Encrypt);
  11.     }
  12.     @Override
  13.     public String readData() throws IOException {
  14.         String readData = super.readData();
  15.         // 解密
  16.         System.out.println("--SM4解密前数据:"+readData);
  17.         String sm4Decrypt = SM4EncryptUtil.sm4Decrypt(readData);
  18.         return sm4Decrypt;
  19.     }
  20. }
复制代码
工具类
  1. public class SM4EncryptUtil {
  2.     // 秘钥
  3.     private static final String key = "1234567812345678";
  4.     static {
  5.         // 添加安全提供者(SM2,SM3,SM4等加密算法,CBC、CFB等加密模式,PKCS7Padding等填充方式,不在Java标准库中,由BouncyCastleProvider实现)
  6.         Security.addProvider(new BouncyCastleProvider());
  7.     }
  8.     /**
  9.      * 输入:待加密的字符串,16或24或32位字符串密码
  10.      * 输出:16进制字符串或Base64编码的字符串密文(常用)
  11.      */
  12.     public static String sm4Encrypt(String encrypt) {
  13.         String cipherString = null;
  14.         try {
  15.             // 指定加密算法
  16.             String algorithm = "SM4";
  17.             // 创建密钥规范
  18.             SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), algorithm);
  19.             // 获取Cipher对象实例(BC中SM4默认使用ECB模式和PKCS5Padding填充方式,因此下列模式和填充方式无需指定)
  20.             Cipher cipher = Cipher.getInstance(algorithm + "/ECB/PKCS5Padding");
  21.             // 初始化Cipher为加密模式
  22.             cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
  23.             // 获取加密byte数组
  24.             byte[] cipherBytes = cipher.doFinal(encrypt.getBytes(StandardCharsets.UTF_8));
  25.             // 输出为Base64编码
  26.             cipherString = Base64.getEncoder().encodeToString(cipherBytes);
  27.         } catch (Exception e) {
  28.             e.printStackTrace();
  29.         }
  30.         return cipherString;
  31.     }
  32.     /**
  33.      * 输入:密文,16或24或32位字符串密码
  34.      * 输出:明文
  35.      */
  36.     public static String sm4Decrypt(String cipherString) {
  37.         String plainString = null;
  38.         try {
  39.             // 指定加密算法
  40.             String algorithm = "SM4";
  41.             // 创建密钥规范
  42.             SecretKeySpec secretKeySpec = new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), algorithm);
  43.             // 获取Cipher对象实例(BC中SM4默认使用ECB模式和PKCS5Padding填充方式,因此下列模式和填充方式无需指定)
  44.             Cipher cipher = Cipher.getInstance(algorithm + "/ECB/PKCS5Padding");
  45.             // 初始化Cipher为解密模式
  46.             cipher.init(Cipher.DECRYPT_MODE, secretKeySpec);
  47.             // 获取加密byte数组
  48.             byte[] cipherBytes = cipher.doFinal(Base64.getDecoder().decode(cipherString));
  49.             // 输出为字符串
  50.             plainString = new String(cipherBytes);
  51.         } catch (Exception e) {
  52.             e.printStackTrace();
  53.         }
  54.         return plainString;
  55.     }
  56. }
复制代码
具体装饰器2--Base64 编码处理
  1. public class Base64SocketStreamDecorator extends SocketStreamDecorator{
  2.     public Base64SocketStreamDecorator(SocketStream socketStream) {
  3.         super(socketStream);
  4.     }
  5.     @Override
  6.     public void writeData(String data) throws IOException {
  7.         String encode = this.encode(data);
  8.         System.out.println("--base64编码后的数据:" + encode);
  9.         super.writeData(encode);
  10.     }
  11.     @Override
  12.     public String readData() throws IOException {
  13.         String readData = super.readData();
  14.         System.out.println("--base64解密前的数据:" + readData);
  15.         String decode = this.decode(readData);
  16.         return decode;
  17.     }
  18.     /**
  19.      * base64编码
  20.      */
  21.     public String encode(String str) {
  22.         if (str == null || str.isEmpty()) {
  23.             return "";
  24.         }
  25.         // String 转 byte[]
  26.         byte[] bytes = str.getBytes(StandardCharsets.UTF_8);
  27.         // 编码(base64字符串)
  28.         return Base64.getEncoder().encodeToString(bytes);
  29.     }
  30.     /**
  31.      * base64解码
  32.      */
  33.     public String decode(String base64Str) {
  34.         if (base64Str == null || base64Str.isEmpty()) {
  35.             return "";
  36.         }
  37.         // 编码
  38.         byte[] base64Bytes = Base64.getDecoder().decode(base64Str);
  39.         // byte[] 转 String(解码后的字符串)
  40.         return new String(base64Bytes, StandardCharsets.UTF_8);
  41.     }
  42. }
复制代码
测试代码

测试Socket 服务端代码
  1. public class SM4SocketServer {
  2.     public static void main(String[] args) throws IOException {
  3.         ServerSocket serverSocket = new ServerSocket(9999);
  4.         System.out.println("Server started...");
  5.         while (true) {
  6.             try (Socket socket = serverSocket.accept()) {
  7.                 System.out.println("Client connected...\n");
  8.                 // 加密数据流
  9.                 SocketStream socketStreamDecorator = new Base64SocketStreamDecorator(
  10.                                                         new SM4CipherSocketStreamDecorator(
  11.                                                           new SimpleSocketStream(socket)));
  12.                 // 读取客户端请求并解密
  13.                 String message = socketStreamDecorator.readData();
  14.                 System.out.println("服务端读取数据: " + message);
  15.                 // 处理请求并加密响应
  16.                 String response = "我来自服务端!";
  17.                 System.out.println("服务端发送数据: " + response);
  18.                 socketStreamDecorator.writeData(response);
  19.             } catch (Exception e) {
  20.                 e.printStackTrace();
  21.             }
  22.         }
  23.     }
  24. }
复制代码
测试Socket 客户端代码
  1. public class SM4SocketClient {
  2.     public static void main(String[] args) throws IOException {
  3.         // 连接到服务器
  4.         try (Socket socket = new Socket("localhost", 9999)) {
  5.             System.out.println("Connected to server...\n");
  6.             // 读取服务器的响应并解密
  7.             SocketStream socketStreamDecorator = new Base64SocketStreamDecorator(
  8.                                                     new SM4CipherSocketStreamDecorator(
  9.                                                       new SimpleSocketStream(socket)));
  10.             // 要发送的消息
  11.             String message = "我来自客户端!";
  12.             System.out.println("客户端发送数据: " + message);
  13.             // 使用加密流将消息发送到服务器
  14.             socketStreamDecorator.writeData(message);
  15.             // 读取客户端请求并解密
  16.             String readData = socketStreamDecorator.readData();
  17.             System.out.println("客户端读取数据: " + readData);
  18.             socketStreamDecorator.close();
  19.         } catch (IOException e) {
  20.             e.printStackTrace();
  21.         }
  22.     }
  23. }
复制代码
测试结果分析

先启动Socket服务,再发起Socket请求。
客户端输出结果
Connected to server...
客户端发送数据: 我来自客户端!
--base64编码后的数据:5oiR5p2l6Ieq5a6i5oi356uv77yB
--SM4加密数据:anTSZv18wcQOxQtA82w9ap2j8DjagsX9QqRVjzFCjWU=
--SM4解密前数据:5azwyKP7MB6jSyG3eDlJmfOYx5y+woud2WgE2Jjf5lA=
--base64解密前的数据:5oiR5p2l6Ieq5pyN5Yqh56uv77yB
客户端读取数据: 我来自服务端!
服务端输出结果
Server started...
Client connected...
--SM4解密前数据:anTSZv18wcQOxQtA82w9ap2j8DjagsX9QqRVjzFCjWU=
--base64解密前的数据:5oiR5p2l6Ieq5a6i5oi356uv77yB
服务端读取数据: 我来自客户端!
服务端发送数据: 我来自服务端!
--base64编码后的数据:5oiR5p2l6Ieq5pyN5Yqh56uv77yB
--SM4加密数据:5azwyKP7MB6jSyG3eDlJmfOYx5y+woud2WgE2Jjf5lA=
这行代码new的先后顺序决定代码执行的先后顺序:new Base64SocketStreamDecorator(new SM4CipherSocketStreamDecorator(new SimpleSocketStream(socket))); ,所以文中的案例测试执行顺序如下:
数据流方向

  • 写入数据时:外层到内层逐步加工数据。

    • 数据先经过 Base64 编码,再经过 SM4 加密,最后写入 SimpleSocketStream。

  • 读取数据时:内层到外层逐步还原数据。

    • 数据从 SimpleSocketStream 读取后,先解密(SM4),再解码(Base64)。

代码层面的调用顺序

  • 构造阶段

    • SimpleSocketStream(socket) → SM4CipherSocketStreamDecorator → Base64SocketStreamDecorator。

  • 方法调用阶段(如 write(data) 或 read()):

    • 调用 Base64SocketStreamDecorator.write(data)。
    • 该方法内部会调用 SM4CipherSocketStreamDecorator.write(data)。
    • 最终调用 SimpleSocketStream.write(data) 执行实际的数据写入。

调整组合顺序测试

如果哪天有需求要改为先执行SM4加密,再执行Base64编码,则代码只需要修改为new SM4CipherSocketStreamDecorator(new Base64SocketStreamDecorator(new SimpleSocketStream(socket)));
测试结果变为:
客户端输出结果
客户端发送数据: 我来自客户端!
--SM4加密数据:YoNCZhEm1shIjPWO5vyhsFRWq+XUdHPggFiE325GCKc=
--base64编码后的数据:WW9OQ1poRW0xc2hJalBXTzV2eWhzRlJXcStYVWRIUGdnRmlFMzI1R0NLYz0=
--base64解密前的数据:OUJTQ0ZLUVFIa2dxSGs0WTd2enB1RlJXcStYVWRIUGdnRmlFMzI1R0NLYz0=
--SM4解密前数据:9BSCFKQQHkgqHk4Y7vzpuFRWq+XUdHPggFiE325GCKc=
客户端读取数据: 我来自服务端!
服务端输出结果变为
--base64解密前的数据:WW9OQ1poRW0xc2hJalBXTzV2eWhzRlJXcStYVWRIUGdnRmlFMzI1R0NLYz0=
--SM4解密前数据:YoNCZhEm1shIjPWO5vyhsFRWq+XUdHPggFiE325GCKc=
服务端读取数据: 我来自客户端!
服务端发送数据: 我来自服务端!
--SM4加密数据:9BSCFKQQHkgqHk4Y7vzpuFRWq+XUdHPggFiE325GCKc=
--base64编码后的数据:OUJTQ0ZLUVFIa2dxSGs0WTd2enB1RlJXcStYVWRIUGdnRmlFMzI1R0NLYz0=
装饰模式的应用

Java标准库中的装饰模式之一:IO流体系
示例代码
  1. BufferedReader bufferedReader = new BufferedReader(
  2.                                           new InputStreamReader(
  3.                                             new FileInputStream(
  4.                                               new File("test.txt"))));
复制代码
逐步增强功能:IO流的增强组合有顺序要求

  • 每一层包装器都增加了功能:

    • File:创建一个表示文件的对象 test.txt;
    • FileInputStream:从文件中读取字节;
    • InputStreamReader:将字节流转换为字符流;
    • BufferedReader:提供字符缓冲功能,支持高效读取和按行读取。

总结

装饰模式是一种灵活的结构型模式,允许我们在不修改现有类的情况下,动态地添加功能。它尤其适合于功能可以按需组合叠加、扩展的场景,但需要权衡复杂性与性能开销。
2.gif

什么是设计模式?
单例模式及其思想
设计模式--原型模式及其编程思想
掌握设计模式之生成器模式
掌握设计模式之简单工厂模式
掌握设计模式之工厂方法模式
超实用的SpringAOP实战之日志记录
2023年下半年软考考试重磅消息
通过软考后却领取不到实体证书?
计算机算法设计与分析(第5版)
Java全栈学习路线、学习资源和面试题一条龙
软考证书=职称证书?
软考中级--软件设计师毫无保留的备考分享

来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

相关推荐

您需要登录后才可以回帖 登录 | 立即注册