经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » Java相关 » 设计模式 » 查看文章
谈谈Java常用类库中的设计模式 - Part Ⅲ
来源:cnblogs  作者:d1zzyboy  时间:2020/12/8 9:08:27  对本文有异议

概述

本系列上一篇:适配器、模版方法、装饰器

本文介绍的设计模式:

策略
观察者
代理

相关缩写:EJ - Effective Java

Here We Go

策略 (Stragety)

定义:定义算法家族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化,不会影响到使用算法的客户。

场景:当不同的行为堆砌在一个类中,难以避免使用条件语句选择行为时,将这些行为封装在独立的策略类中,可以消除条件语句;一个系统需要动态地选择一种算法时。

类型:行为型

相比之前提到的设计模式,策略的使用更加广泛,它简单直观,作用强大,只要业务中存在同一场景会有不同的处理规则,就可以用到策略。
在Java类库中使用最频繁的策略当属Comparator。

  1. @FunctionalInterface
  2. public interface Comparator<T> {
  3. int compare(T o1, T o2);
  4. }

Comparator的角色是抽象策略(abstract stragety),而实现了Comparator的具体排序规则就是具体策略(concrete strategy)。

在JDK 1.8的Comparator中还实现了诸多缺省方法,例如翻转排序、空值优先、排序规则链等,这里使用了模版方法的思想。

在客户端代码中实现好比较算法,剩下的排序工作,交给类库去做即可。

  1. Collections.sort(studentList, new Comparator<Student>() {
  2. @Override
  3. public int compare(Student a, Student b) {
  4. return a.getScore()-b.getScore();
  5. }
  6. });

当然在实际生产中使用更简便易读的comparing方法,岂不美哉?

  1. Collections.sort(studentList,Comparator.comparing(Student::getScore));

除了Comparator,还有一个典型案例:线程池的饱和策略。以下是带有指定饱和策略的线程池构造方法(最后一个入参RejectedExecutionHandler handler就是指定饱和策略)

  1. public ThreadPoolExecutor(int corePoolSize,
  2. int maximumPoolSize,
  3. long keepAliveTime,
  4. TimeUnit unit,
  5. BlockingQueue<Runnable> workQueue,
  6. RejectedExecutionHandler handler) {
  7. this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
  8. Executors.defaultThreadFactory(), handler);
  9. }

饱和策略将在有界队列被填满后触发,我们无需关心策略的具体调用,只需专注于如何设计策略使得线程池更加健壮。以下是ThreadPoolExecutor提供的四种预设策略中的抛弃最旧策略:

  1. public static class DiscardOldestPolicy implements RejectedExecutionHandler {
  2. /**
  3. * Creates a {@code DiscardOldestPolicy} for the given executor.
  4. */
  5. public DiscardOldestPolicy() { }
  6. /**
  7. * Obtains and ignores the next task that the executor
  8. * would otherwise execute, if one is immediately available,
  9. * and then retries execution of task r, unless the executor
  10. * is shut down, in which case task r is instead discarded.
  11. *
  12. * @param r the runnable task requested to be executed
  13. * @param e the executor attempting to execute this task
  14. */
  15. public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
  16. if (!e.isShutdown()) {
  17. e.getQueue().poll();
  18. e.execute(r);
  19. }
  20. }



观察者 (Observer)

定义:定义一种一对多的依赖关系,让多个观察者对象同时监听某一主题对象。这个主题对象在状态发生变化时,会通知所有观察者对象,使它们能够自动更新自己。

场景:当一个对象的改变需要同时改变其它对象的时候。

类型:行为型

观察者有一个更著名的名字:发布-订阅模型(Pub/Sub),在Redis、MQ中的使用非常广泛。实际上JDK在1.0版本就编写了观察者的支持类。

观察者需要实现Observer接口,在主题通知时将调用updae方法更新观察者自己的状态。

  1. public interface Observer {
  2. void update(Observable o, Object arg);
  3. }

主题对象已经被JDK实现,被观察者发生变化时调用notifyObservers通知观察者,观察者队列由系统维护。(类库开发较早且没有维护,观察者集合仍然使用Vector。)

  1. public class Observable {
  2. private boolean changed = false;
  3. private Vector<Observer> obs;
  4. public Observable() {
  5. obs = new Vector<>();
  6. }
  7. public synchronized void addObserver(Observer o) {
  8. if (o == null)
  9. throw new NullPointerException();
  10. if (!obs.contains(o)) {
  11. obs.addElement(o);
  12. }
  13. }
  14. public void notifyObservers(Object arg) {
  15. /*
  16. * a temporary array buffer, used as a snapshot of the state of
  17. * current Observers.
  18. */
  19. Object[] arrLocal;
  20. synchronized (this) {
  21. if (!changed)
  22. return;
  23. arrLocal = obs.toArray();
  24. clearChanged();
  25. }
  26. for (int i = arrLocal.length-1; i>=0; i--)
  27. ((Observer)arrLocal[i]).update(this, arg);
  28. }
  29. ...省略其它方法
  30. }

值得一提的是,通知观察者的方法notifyObservers在尽力缩小锁粒度:因为观察者执行变更的代码主题对象无法控制,此段时间不需要也不应该持有主题对象的锁,于是这里加锁复制了一个当前观察者队列的快照,在执行通知前释放锁。遵循了避免过渡同步[EJ Item 79]的原则。




代理 (Proxy)

定义:为其它对象提供一种代理以控制对这个对象的访问。

场景:为一个对象在不同的地址空间提供局部代表,隐藏一个对象存在于不同地址空间的事实;控制真实对象访问时的权限;调用真实对象时,代理可以附加其它逻辑。

类型:结构型

代理的实现与装饰器的实现:复合-转发 基本相同,需要代理类继承公共接口,并持有被代理类的实例进行转发。所以二者在结构和功能上非常相似,但比起追加功能,代理强调的是通信控制屏蔽
谈到Java类库中对代理的实践,那必然是动态代理了,实际上动态代理比代理更加先进:代理本身是结构型设计模式,但在反射的加持下,开发者可以将代理结构延迟到运行时动态构建,这就是动态代理。

  1. List<Integer> trueList = Collections.emptyList();
  2. //创建一个代理类,实现特定接口,并将调用分发到指定的调用处理器上
  3. List proxyList = (List) Proxy.newProxyInstance(List.class.getClassLoader(),
  4. new Class[]{List.class}, (proxy, method, args) -> {
  5. System.out.println("enter invoke handler");
  6. return method.invoke(trueList,args);
  7. });
  8. System.out.println(proxyList.size());
  9. 输出
  10. ----------------
  11. enter invoke handler
  12. 0

通过Proxy.newProxyInstance即可创建代理类,代理类实现了指定接口,并且任何方法调用将被分发到InvocationHandler上,它持有真正被代理的对象,在执行完代理操作后,便可以将调用转发到真实对象上。
许多成熟框架都使用了动态代理来增强用户代码,例如Mybatis的MapperProxy,SpringAOP等等。




参考:

[1] Effective Java - 机械工业出版社 - Joshua Bloch (2017/11)

[2] 《大话设计模式》 - 清华大学出版社 - 陈杰 (2007/12)

原文链接:http://www.cnblogs.com/notayeser/p/14032918.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号