• 谈谈Java常用类库中的设计模式


    概述

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

    本文介绍的设计模式:

    策略
    观察者
    代理

    相关缩写:EJ - Effective Java

    Here We Go

    策略 (Stragety)

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

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

    类型:行为型

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

    @FunctionalInterface
    public interface Comparator<T> {
        int compare(T o1, T o2);
    }
    

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

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

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

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

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

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

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

        public ThreadPoolExecutor(int corePoolSize,
                                  int maximumPoolSize,
                                  long keepAliveTime,
                                  TimeUnit unit,
                                  BlockingQueue<Runnable> workQueue,
                                  RejectedExecutionHandler handler) {
            this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
                 Executors.defaultThreadFactory(), handler);
        }
    

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

    public static class DiscardOldestPolicy implements RejectedExecutionHandler {
            /**
             * Creates a {@code DiscardOldestPolicy} for the given executor.
             */
            public DiscardOldestPolicy() { }
    
            /**
             * Obtains and ignores the next task that the executor
             * would otherwise execute, if one is immediately available,
             * and then retries execution of task r, unless the executor
             * is shut down, in which case task r is instead discarded.
             *
             * @param r the runnable task requested to be executed
             * @param e the executor attempting to execute this task
             */
            public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
                if (!e.isShutdown()) {
                    e.getQueue().poll();
                    e.execute(r);
                }
            }
    



    观察者 (Observer)

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

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

    类型:行为型

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

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

    public interface Observer {
        void update(Observable o, Object arg);
    }
    

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

    public class Observable {
        private boolean changed = false;
        private Vector<Observer> obs;
    
        public Observable() {
            obs = new Vector<>();
        }
    
        public synchronized void addObserver(Observer o) {
            if (o == null)
                throw new NullPointerException();
            if (!obs.contains(o)) {
                obs.addElement(o);
            }
        }
    
        public void notifyObservers(Object arg) {
            /*
             * a temporary array buffer, used as a snapshot of the state of
             * current Observers.
             */
            Object[] arrLocal;
    
            synchronized (this) {
                if (!changed)
                    return;
                arrLocal = obs.toArray();
                clearChanged();
            }
    
            for (int i = arrLocal.length-1; i>=0; i--)
                ((Observer)arrLocal[i]).update(this, arg);
        }
        
        ...省略其它方法
    }
    

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




    代理 (Proxy)

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

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

    类型:结构型

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

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

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




    参考:

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

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

  • 相关阅读:
    mysql中文无法显示 小强斋
    EJB>EJB 概念 小强斋
    EJB>EJB 概念 小强斋
    Hibernate>主要接口 小强斋
    EJB>EJB 概念 小强斋
    Hibernate>主要接口 小强斋
    EJB>Session Beans 小强斋
    mysql中文无法显示 小强斋
    如何使用PLSQL Developer从oracle数据库导入导出数据 小强斋
    如何使用PLSQL Developer从oracle数据库导入导出数据 小强斋
  • 原文地址:https://www.cnblogs.com/notayeser/p/14032918.html
Copyright © 2020-2023  润新知