谈谈Java常用类库中的设计模式 - Part Ⅲ

概述

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

本文介绍的设计模式:

策略
观察者
代理

相关缩写: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)

(0)

相关推荐

  • 设计模式(20) 观察者模式

    观察者模式是一种平时接触较多的模式.它主要用于一对多的通知发布机制,当一个对象发生改变时自动通知其他对象,其他对象便做出相应的反应,同时保证了被观察对象与观察对象之间没有直接的依赖. GOF对观察者模 ...

  • 聊聊 Python 面试最常被问到的几种设计模式(下)

    聊聊 Python 面试最常被问到的几种设计模式(下)

  • 设计模式中的观察者模式

    观察者模式是一种软件设计模式,其中一个名为主体(Subject)的对象维护其依赖项列表,称为观察者,并通常通过调用它们(observers)的方法之一来自动通知它们任何状态更改. 观察者模式主要用于在 ...

  • 通俗易懂系列 | 设计模式(七):观察者模式

    介绍# 观察者模式是行为设计模式之一.当您对对象的状态感兴趣并希望在有任何更改时收到通知时,观察者设计模式非常有用.在观察者模式中,监视另一个对象状态的对象称为Observer,正在被监视的对象称为S ...

  • JavaScript设计模式之观察者模式

    目录 简介 实现 创建观察者对象 简介 观察者模式由称作发布-订阅者模式或消息机制,该模式定义一种依赖关系,旨在解决主体对象与观察者之间功能的耦合. 例如案例:想实现一个评论模块,当用户发送消息时,在 ...

  • 常见的设计模式及应用场景

    一.单例模式 单例模式是一种常用的软件设计模式,在应用这个模式时,单例对象的类必须保证只有一个实例存在,整个系统只能使用一个对象实例. 优点:不会频繁地创建和销毁对象,浪费系统资源. 使用场景:IO ...

  • 阿里面试官亲述:如何利用设计模式改善业务代码

    在业务部门的开发中,大多数的我们在完成的业务的各种需求和提供解决方案,很多场景下的我们通过 CRUD 就能解决问题,但是这样的工作对技术人的提升并不多,如何让自己从业务中解脱出来找到写代码的乐趣呢,我 ...

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

    概述 本系列上一篇:建造者.工厂方法.享元.桥接 本文介绍的设计模式(建议按顺序阅读): 适配器 模板方法 装饰器 相关缩写:EJ - Effective Java Here We Go 适配器 (A ...

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

    背景 最近一口气看完了Joshua Bloch大神的Effective Java(下文简称EJ).书中以tips的形式罗列了Java开发中的最佳实践,每个tip都将其意图和要点压缩在了标题里,这种做法 ...

  • Java常用类库(一):

    顶哥说:Java是世界的,但项目不是! Java有非常多的类库,而我们不会也不用都去学习,毕竟你也仅仅掌握了你手机20%的功能却足够你使用,不是吗? 今天简单介绍以下类: Object Date Da ...

  • Java常用类库(二)

    顶哥说Java是世界的,但项目不是. 今天介绍集合类的以下内容: Iterator迭代器 子范围视图简介 Iterator迭代器: 应该将java迭代器认为是位于两个元素之间, 当调用next 时,迭 ...

  • Java中的设计模式(一):观察者模式

    工具与资源中心 帮助开发者更加高效的工作,提供围绕开发者全生命周期的工具与资源https://developer.aliyun.com/tool?spm=a1z389.11499242.0.0.654 ...

  • 家中常备7种儿童常用的中成.药,几块钱轻...

    家中常备7种儿童常用的中成.药,几块钱轻松解决孩子厌食.消化不良大便干结.感冒   1.小儿豉翘清热颗粒   比较多用于小儿风热感冒滞证,症见发热咳嗽,鼻塞流涕,咽红肿痛,纳呆口渴,脘腹胀满,便秘或者 ...

  • 常用的中成.药,对脾气暴躁,烦躁不安,自汗盗汗的女性管用

    常用的中成.药,对脾气暴躁,烦躁不安,自汗盗汗的女性管用 1,舒更胶囊 中成药,成分:由豆蔻.肉豆蔻.沉香.丁香.天冬.黄精.手参组成. 功效作用:具有调和气血,安神的功效. 临床应用:用于妇女更年期 ...

  • '湿气'重!中医5个常用祛湿中药方!既是...

    '湿气'重!中医5个常用祛湿中药方!既是方药,也是祛湿之法.建议分享收藏! 1.芳香化湿----平胃散 症状:脘腹胀满.不思饮食.口淡无味.呕吐恶心.嗳气吞酸.肢体困重,倦怠乏力.舌苔白腻.脉缓. 平 ...

  • 古代用药剂量为什么与现在不同?谈谈经方剂量研究中被忽视的问题

    经方对于中医药人来说再熟悉不过了,历代中医都是在熟读经方的基础上成为名医的.近年来,经典名方的研究很多,其中很大一部分就是对经方的研究.研究经典名方需要做很多工作,如中药来源的考证.炮制方法的考证.处 ...