主页 > 游戏开发  > 

装饰器模式

装饰器模式

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

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中加入时间戳就可以了。最后只要可以将其放入HandlerMethodArgumentResolverComposite的List<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()方法设置的。因此接下来给该方法打上断点,查看该方法是如何被调用的。 发现resolvers是this.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)); } }
标签:

装饰器模式由讯客互联游戏开发栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“装饰器模式