当前位置: 首页 > news >正文

装饰器模式

参考 装饰者模式
【设计模式实战】装饰器模式

1. HistorySet的例子

HistorySet 可以在实现的Set的基础上,在remove时保留删除的元素。通过将方法委托给现有的Set,在remove时先保留被删除元素后委托给注入的set进行remove

public class HistorySet<E>  implements Set<E> {
    private final List<E> removeList=new ArrayList<>();
    private final Set<E> delegate;

    public HistorySet(Set<E> set){
        this.delegate=set;

    }

    @Override
    public int size() {
        return this.delegate.size();
    }

    @Override
    public boolean isEmpty() {
        return this.delegate.isEmpty();
    }

    @Override
    public boolean contains(Object o) {
        return this.delegate.contains(o);
    }

    @Override
    public Iterator<E> iterator() {
        return  this.delegate.iterator();
    }

    @Override
    public Object[] toArray() {
        return this.delegate.toArray();
    }

    @Override
    public <T> T[] toArray(T[] a) {
        return this.delegate.toArray(a);
    }

    @Override
    public boolean add(E s) {
        return this.delegate.add(s);
    }

    @Override
    public boolean remove(Object o) {
        boolean result=false;
        if( this.delegate.contains(o)){
            this.removeList.add((E) o);
            this.delegate.remove(o);
        }
        return result;
    }

    @Override
    public boolean containsAll(Collection<?> c) {
        return this.delegate.containsAll(c);
    }

    @Override
    public boolean addAll(Collection<? extends E> c) {
        return this.delegate.addAll(c);
    }

    @Override
    public boolean retainAll(Collection<?> c) {
        return this.delegate.retainAll(c);
    }

    @Override
    public boolean removeAll(Collection<?> c) {
        return this.delegate.removeAll(c);
    }

    @Override
    public void clear() {
        this.delegate.clear();

    }

    @Override
    public String toString() {
        return "HistorySet{" +
                "removeList=" + removeList +
                ", delegate=" + delegate +
                '}';
    }


    public static void main(String[] args) {
        HistorySet<String> historySet = new HistorySet<>(new HashSet<>());
        historySet.add("age");
        historySet.add("sex");
        historySet.add("interest");
        historySet.remove("sex");
        for (String s : historySet.removeList) {
            System.out.println(s);
        }


        System.out.println(historySet.toString());

        Collection<Object> objects = Collections.synchronizedCollection(new ArrayList<>());
        //不是原子操作
        if(objects.isEmpty()){
            objects.add(1);
        }


    }
}

2. BufferInputStream

2.1 原始的读取文件的代码

由于采用fileInputStream.read()一个一个字节读入,每次读入都要重新启动IO操作,费时费力。

public class MyFile {
    public static void main(String[] args) {
        File file = new File(MyFile.class.getClassLoader().getResource("test.pdf").getPath());
        long l = Instant.now().toEpochMilli();
        try (InputStream fileInputStream=new FileInputStream(file)){
            while (true){
                int read = fileInputStream.read();
                if(read== -1){
                    break;
                }
            }
            System.out.println("用时:"+(Instant.now().toEpochMilli()-l)+"毫秒");

        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

2.2 增强后的代码

因此,可以采用装饰器模式,在原有的FileInputStream的read()方法上,为 FileInputStream 添加了缓冲功能,显著提升了读取效率

public class BufferInputStream extends InputStream {

    private final byte[] buffer = new byte[8192]; // 缓冲区
    private int position;       // 当前读取位置
    private int count;          // 缓冲区有效数据长度
    private final FileInputStream fileInputStream;

    public BufferInputStream(FileInputStream fileInputStream) {
        this.fileInputStream = fileInputStream;
    }

    @Override
    public int read() throws IOException {
        // 如果缓冲区已读完,尝试填充
        if (position >= count) {
            fillBuffer();
            if (count == -1) {
                return -1; // 流已结束
            }
        }
        // 返回缓冲区的下一个字节(转换为 0~255)
        return buffer[position++] & 0xFF;
    }

    /**
     * 从底层 FileInputStream 填充缓冲区
     */
    private void fillBuffer() throws IOException {
        count = fileInputStream.read(buffer); // 读取数据到缓冲区
        position = 0; // 重置读取位置
    }

    @Override
    public void close() throws IOException {
        fileInputStream.close(); // 关闭底层流
        super.close();
    }

    public static void main(String[] args) {
        String filePath = BufferInputStream.class.getClassLoader().getResource("a.txt").getPath(); // 替换为你的文件路径
        try (
                // 1. 创建底层文件流
                FileInputStream fis = new FileInputStream(filePath);
                // 2. 包装为缓冲流
                BufferInputStream bis = new BufferInputStream(fis)
        ) {
            int byteData;
            // 3. 逐字节读取文件内容
            while ((byteData = bis.read()) != -1) {
                // 处理字节数据(例如打印字符)
                System.out.print((char) byteData);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

3. ReadCounterInputStream

通过装饰器模式,记录inputStream当前调用过多少次read(方法

public class ReadCounterInputStream extends InputStream {
    private final InputStream target;  // 被装饰的输入流
    private int readCount = 0;         // 记录 read() 调用次数

    public ReadCounterInputStream(InputStream target) {
        this.target = target;
    }

    // 获取当前 read() 方法调用次数
    public int getReadCount() {
        return readCount;
    }

    // 重写所有 read 方法,确保完整计数
    @Override
    public int read() throws IOException {
        readCount++;
        return target.read();
    }

    @Override
    public int read(byte[] b) throws IOException {
        readCount++;
        return target.read(b);
    }

    @Override
    public int read(byte[] b, int off, int len) throws IOException {
        readCount++;
        return target.read(b, off, len);
    }

    @Override
    public void close() throws IOException {
        target.close();
    }

    // 可选:重写其他需要委托的方法(如 available()、skip() 等)
    @Override
    public int available() throws IOException {
        return target.available();
    }


    public static void main(String[] args) throws IOException {
        // 原始输入流(这里以字节数组流为例)
        InputStream rawStream = new ByteArrayInputStream("Hello World".getBytes());

        // 包装为计数装饰器
        ReadCounterInputStream counterStream = new ReadCounterInputStream(rawStream);

        // 读取操作
        counterStream.read();                   // 单字节读取
        counterStream.read(new byte[10], 0, 3); // 带偏移量的读取

        System.out.println("Total read() calls: " + counterStream.getReadCount()); // 输出 3
        counterStream.close();
    }
}

4. 自定义注解,Map 自动加入事件戳

4.1 map是在什么时候生成的

关键是要弄清楚map是在什么时候生成的。

@RestController
@RequestMapping("/")
public class WebController {

    @PostMapping
    public Map<String, String> test(@RequestResponseBody  Map<String,String> map){
        return map;
    }
}

return map语句打断点进行调试。发现调用this.resolvers.resolveArgument后生成了Map对象.

在这里插入图片描述

查看this.resolvers的具体类型:发现this.resolvers HandlerMethodArgumentResolverComposite类对象,
在这里插入图片描述

其中,HandlerMethodArgumentResolverComposite实现了HandlerMethodArgumentResolver接口,并且有类成员List<HandlerMethodArgumentResolver>。毫无疑问,这里利用了装饰者模式,在增强代码的职责后,将底层的代码委托给这些List中类成员实现。(因此我们可以有这样一个印象,Composite中装入许多同类性的对象,必要时将方法委托给它们实现,而这些委托对象又层层委托给底下的委托对象实现,因此到看到某一层的委托就可以了)

在这里插入图片描述
进入 HandlerMethodArgumentResolver类的resolveArgument()方法体中
在这里插入图片描述

发现该方法首先先择合适的解析器resolver,然后委托其实现resolver.resolveArgument()后生成了args[i]。这个方法的核心就是如何先择合适的解析器resolver
因此接下来我们主要分为两步骤,查看getArgumentResolve()和断点调试解析到的具体的resolver类型查看其是如何实现的。

4.11 查看getArgumentResolve()方法体

在这里插入图片描述
发现主要是利用resolver.supportsParameter(parameter)选择合适的解析器。

4.12 查看resolver的具体类型

return result 打上断点,可以看见result解析器类型为RequestResponseBodyMethodProcessor。查看其源码:
在这里插入图片描述
由于 HandlerMethodArgumentResolver resolver = this.getArgumentResolver(parameter);使用HandlerMethodArgumentResolver 接收,因此只需查看HandlerMethodArgumentResolver接口中的方法。
在这里插入图片描述
在这里插入图片描述

会发现这里用到了RequestBody.class。点击进入该注解:
在这里插入图片描述

原来我们使用的@RequestBody注解在这里使用到了,因此,我们可以定义一个HandlerMethodArgumentResolve类, 在遇到自定义注解时,supportsParam()方法返回true,就可以调用自定义的解析器的resolve方法,只要我们在resolve()生成实例时加入时间戳就可以了。

因此我们可以自定义一个HandlerMethodArgumentResolver类,当遇到参数上有@TimestampResponseBody返回true,即可选中该解析对象,在调用解析对象resolve后在map中加入时间戳就可以了。最后只要可以将其放入HandlerMethodArgumentResolverCompositeList<HandlerMethodArgumentResolver>中就可以了。

模仿RequestResponseBodyMethodProcessor类,写出自定义的类。为了能够简化代码,可以利用装饰器模式定义自定义解析器

public class TimestampBodyMethodProcessor implements HandlerMethodArgumentResolver {

    private  RequestResponseBodyMethodProcessor processor;


    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return  parameter.hasParameterAnnotation(TimestampResponseBody.class);
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        Object o = processor.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
        if(!(o instanceof Map<?,?>)){
            return o;
        }
        ((Map) o).put("timestamp","11233333");
        return o;
   }
}

模仿@RequestBody 自定义注解@TimestampResponseBody
在这里插入图片描述

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TimestampResponseBody {
}

4.2 在HandlerMethodArgumentResolverComposite中的List中放入自定义解析器

发现原始的argumentResolvers为空,调用addResolver()方法后才加入resolver。在addResolver()打上断点,看时哪个函数调用的。
在这里插入图片描述
发现在resolvers = this.getDefaultArgumentResolvers();生成resolvers
在这里插入图片描述
进入resolvers = this.getDefaultArgumentResolvers();
在这里插入图片描述
发现在这里预留了加入在resolvers中加入自定义的resolvers的方法即this.getCustomArgumentResolvers()
在这里插入图片描述
进入this.getCustomArgumentResolvers()方法
在这里插入图片描述
发现this.customArgumentResolvers通过setCustomArgumentResolvers()方法设置的。因此接下来给该方法打上断点,查看该方法是如何被调用的。
在这里插入图片描述
发现resolversthis.getArgumentResolvers()产生的,进入该方法中
在这里插入图片描述
发现实际加入的自定义resolvers是由addArgumentResolvers()产生的。因此继续查看实现的方法。
在这里插入图片描述
在这里插入图片描述
发现该类也利用了装饰器模式,是调用this.configures的方法进行添加的,并且仔细查看发现该类中的configures是通过外部注入的方式。然后查看该类的addArgumentResolvers()方法,发现
在这里插入图片描述

发现addArgumentResolvers()是依次调用this.delegates中每个delegate.addArgumentResolvers(),并且使用该对象时时通过外部注入的方式为this.delegates赋值,
因此我们可以自定义类实现WebMvcConfigurationSupport接口中的
this.configurers.addArgumentResolvers(argumentResolvers)方法,并让该类注入到spring的容器,然后利用装饰器模式,在委托对象加入argumentResolvers之前拦截argumentResolvers,在其中加入自定义的解析器。

@Component
public class MyWebMvcConfigure implements WebMvcConfigurer {

    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(new TimestampBodyMethodProcessor());
    }
}

4.3 获取RequestResponseBodyMethodProcessor

在自定义 HandlerMethodArgumentResolver类中,发现processor没有办法获得。在查看解析器那一部分源码中,我们发现这些解析器都是有系统new出来的,没有交给容器管理,因此,必须采用其它方法获得。在浏览源码中,发现这些new出来的解析器都在 HandlerMethodArgumentResolverComposite的肚子里,因此可以首先获取容器,然后取出该对象,从肚子中获取就可以了。

public class TimestampBodyMethodProcessor implements HandlerMethodArgumentResolver {

    private  RequestResponseBodyMethodProcessor processor;

    private ApplicationContext applicationContext;
    public TimestampBodyMethodProcessor(ApplicationContext applicationContext){
        this.applicationContext=applicationContext;
    }

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return  parameter.hasParameterAnnotation(TimestampResponseBody.class);
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        setupProcessor();
        Object o = processor.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
        if(!(o instanceof Map<?,?>)){
            return o;
        }
        ((Map) o).put("timestamp","11233333");
        return o;
    }

    private void setupProcessor() {
        if(this.processor ==null){
            RequestMappingHandlerAdapter bean = this.applicationContext.getBean(RequestMappingHandlerAdapter.class);
            List<HandlerMethodArgumentResolver> argumentResolvers = bean.getArgumentResolvers();
            for(HandlerMethodArgumentResolver argumentResolver:argumentResolvers){
                if(argumentResolver instanceof  RequestResponseBodyMethodProcessor){
                    this.processor= (RequestResponseBodyMethodProcessor) argumentResolver;
                    return;
                }
            }
        }

    }
}
@Component
public class MyWebMvcConfigure implements WebMvcConfigurer {
    @Resource
    private ApplicationContext applicationContext;


    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(new TimestampBodyMethodProcessor(applicationContext));
    }
}

相关文章:

  • 双指针-三数之和
  • 【YOLOv11改进- 主干网络】YOLOv11+CSWinTransformer: 交叉窗口注意力Transformer助力YOLOv11有效涨点;
  • MongoDB:记一次数据迁移经验
  • JavaSE的基础语法(5)
  • PostgreSQL如何关闭自动commit
  • 基于Python的Flask微博话题舆情分析可视化系统
  • SaaS 平台开发要点
  • javascript-es6 (四)
  • 【NLP251】命名实体实战(基于Transformer分类)
  • 【BUUCTF逆向题】[ACTF新生赛2020]Splendid_MineCraft(SMC代码混淆)
  • 【强化学习】强化学习(Reinforcement Learning, RL)详解
  • SpringBoot+uniApp日历备忘录小程序系统 附带详细运行指导视频
  • 459重复的子字符串(substr)
  • Word中的文档信息域
  • Java语言介绍
  • cap2:1000分类的ResNet的TensorRT部署指南(python版)
  • Linux:深入了解进程信号(上)
  • (7/100)每日小游戏平台系列
  • 力扣做题记录 (二叉树)
  • MySQL 插入替换语句(replace into statement)
  • 426.8万人次!长三角铁路创单日客发量历史新高
  • 中国代表:美“对等关税”和歧视性补贴政策严重破坏世贸规则
  • 夜读丨春天要去动物园
  • 何立峰出席驻沪中央金融机构支持上海建设国际金融中心座谈会并讲话
  • 西藏阿里地区日土县连发两次地震,分别为4.8级和3.8级
  • 习近平访问金砖国家新开发银行