设计模式学习笔记 - 开源实战一(下):通过剖析JDK源码学习灵活应用设计模式

这篇具有很好参考价值的文章主要介绍了设计模式学习笔记 - 开源实战一(下):通过剖析JDK源码学习灵活应用设计模式。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

概述

上篇文章我们讲解了工厂模式、建造者模式、适配器模式适配器模式在 JDK 中的应用,其中 Calendar 类用到了工厂模式和建造者模式, Collections 类用到了装饰器模式和适配器模式。学习的重点是让你了解,在真实的项目中模式的实现和应用更加灵活、多变,会根据具体的场景做实现或者设计上的调整。

本章,再重点讲一下模板模式、观察者模式这两个模式在 JDK 中的应用。此外,还会在对理论部分已经经过的一些模式在 JDK 中的应用做一个汇总,带你一起回忆复习下。


模版模式在 Collections 类中的应用

策略、模板、职责链这三个模式常用在框架的设计中,提供框架的扩展点,让框架使用者在不修改框架源码的情况下,基于扩展点定制化框架的功能。Java 中的 Collections 类的 sort() 函数就是利用了模板模式的这个扩展特性。

首先,我们看下 Collections.sort() 是如何使用的。下面写了一个实例代码,如下所示。这个代码实现了按照不同的排序方式(按照年龄从小到大、按照字母序从小到大、按照成绩从大到小)对 students 数组进行排序。

public class Demo {
    public static void main(String[] args) {
        List<Student> students = new ArrayList<>();
        students.add(new Student("Jack", 19, 91));
        students.add(new Student("Lucky", 21, 92));
        students.add(new Student("Yaml", 20, 91));

        Collections.sort(students, new AgeAscComparator());
    }

    private static class AgeAscComparator implements Comparator<Student> {
        @Override
        public int compare(Student o1, Student o2) {
            return o1.getAge() - o2.getAge();
        }
    }

    private static class NameAscComparator implements Comparator<Student> {
        @Override
        public int compare(Student o1, Student o2) {
            return o1.getName().compareTo(o2.getName());
        }
    }

    private static class ScoreDescComparator implements Comparator<Student> {
        @Override
        public int compare(Student o1, Student o2) {
            if (Math.abs(o1.getScore() - o2.getScore()) < 0.1f) {
                return 0;
            } else if (o1.getScore() < o2.getScore()) {
                return 1;
            } else {
                return -1;
            }
        }
    }
}

结合刚刚这个例子,再来看下,为什么说 Collections.sort() 函数用到了模板模式?

Collections.sort() 实现了对集合的排序。为了扩展性,它将其中 “比较大小” 这部分逻辑,委派给用户来实现。如果我们把比较大小这部分逻辑看做整个排序逻辑中的其中一个步骤,那我们就可以把它看做模板模式。不过,从代码实现角度来看,它看起来有点类似 JdbcTemplate,并不是模板模式的经典代码实现,而是基于 Callback 回调机制来实现。

不过,有的资料中把 Collections.sort() 看做策略模式。这样的说法也不是没有道理。如果我们并不把 “比较大小” 看做排序逻辑中的一个步骤,而是看作一种算法或者策略,那我们就可以把它看作是策略模式的一种应用。

不过,这也不是经典的策略模式,前面讲过,在经典的策略模式中,策略模式分为策略的定义、创建、使用这三部分。策略通过工厂模式来创建,并且在程序运行期间,根据配置、用户输入、计算结果等这些不确定因素,动态决定使用哪种策略。而在 Collections.sort() 函数中,策略的创建并非通过工厂模式,策略的使用也非动态确定。

观察者模式在 JDK 中的应用

在讲观察者模式时,我们重点讲解了 Google Guava 的 EventBus 框架,它提供了观察者模式的骨架代码。使用 EventBus,我们不需要从零开始开发观察者模式。实际上,Java JDK 也提供了观察者模式的简单框架实现。在平时的开发中,如果我们不希望引入 Google Guava 开发库,可以直接使用 Java 语音本身提供的这个框架类。

不过,它比 EventBus 简单多了,只包含两个类: java.util.Observablejava.util.Observer。前者是被观察者,后者是观察者。它们的代码实现也非常简单,为了方便你查看,我直接复制到这里。

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

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 synchronized void deleteObserver(Observer o) {
        obs.removeElement(o);
    }
    
    public void notifyObservers() {
        notifyObservers(null);
    }

    public void notifyObservers(Object arg) {
        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);
    }

    public synchronized void deleteObservers() {
        obs.removeAllElements();
    }

    protected synchronized void setChanged() {
        changed = true;
    }

    protected synchronized void clearChanged() {
        changed = false;
    }
    
    public synchronized boolean hasChanged() {
        return changed;
    }

    public synchronized int countObservers() {
        return obs.size();
    }
}

对于 ObservableObserver 的代码实现,大部分都很好理解,我们重点来看其中的两个地方。一个是 changed 成员变量,另一个是 notifyObservers() 函数。

先看 changed 成员变量

它用来表面被观察者(Observable)有没有状态更新。当有状态更新时,我们需要手动调用 setChange() 函数,将 changed 变量设置为 true,这样才能在调用 notifyObservers() 函数时,真正触发观察者(Observer)执行 update() 函数。否则,即使你调用了 notifyObservers() 函数,观察者的 update() 函数也不会被执行。

也就是说,当通知观察者被观察者状态更新的时候,我们需要依次调用 setChange()notifyObservers() 两个函数,单独调用 notifyObservers() 是不起作用的。

再看 notifyObservers() 函数

为了保证多线程环境下,添加、移除、通知观察者三个操作之间不发生冲突,Observable 类中的大部分函数都通过 synchronized 加了锁,不过,也有特例,notifyObservers() 函数就没有加 synchronized 锁。这是为什么呢?在 JDK 的代码实现中,notifyObservers() 函数是如何保证跟其他函数操作不冲突的呢?这种加锁方法是否存在问题?又存在什么问题?

notifyObservers() 之所以没有在函数上加一把大锁,主要还是处于性能的考虑。

notifyObservers() 函数依次执行每个观察者的 update() 函数,每个 update() 函数执行的逻辑未知,有可能会耗时。如果在 notifyObservers() 函数上加 synchronized 锁,notifyObservers() 函数持有锁的事件可能会很长,这就会导致其他线程迟迟获取不到锁,影响整个 Observable 类的并发性能。

我们知道,Vector 类不是线程安全地,在多线程环境下,同时添加、删除、遍历 Vector 对象中的元素,会出现不可预期的结果。所以,在 JDK 的代码实现中,为了避免直接给 notifyObservers() 加锁而出现性能问题,JDK 采用了一种折中的方案。这个方案有点类似于我们之前讲过的让迭代器支持 “快照” 的解决方案。

notifyObservers() 函数中,我们先拷贝一份观察者列表,赋值给函数的局部变量,局部变量是线程私有的,并不在线程间共享。这个拷贝出来的线程私有的观察者列表就相当于一个快照。我们变量快照,逐一执行每个观察者的 update() 函数。而这个遍历执行的过程是在快照这个局部变量上操作的,不存在线程安全问题,不需要加锁。所以,我们只需要拷贝创建快照的过程加锁,加锁的范围减少了很多,并发性能提高了。

为什么说这是一种折中的方案呢?这是因为,这种加锁方法实际上是存在一些问题的。**在创建快照之后,添加、删除观察者并不会更新快照,新加入的观察者就不会被通知到,新删除的观察者仍然会被通知到。**这种权衡是否能接受完全看你的业务场景。实际上,这种处理方式也是多线程中减少锁粒度、提高并发性能的常用方法。

单例模式在 Runtime 类中的应用

JDK 中 java.lang.Runtime 类就是一个单例类。在我们之前讲到 Callback 回调的时候,添加 shutdown hook 就是通过这个类实现的。

每个 Java 应用在运行时会启动一个 JVM 进程,每个 JVM 进程都只对应一个 Runtime 实例,用于查看 JVM 状态以及控制 JVM 行为。进程内唯一,所以比较适合设计为单例。在编程时,我们不能自己去实例化一个 Runtime 对象,只能通过 getRuntime() 静态方法来获得。

Runtime 类的代码实现如下所示。这里只包含部分相关代码,其他代码做了省略。从代码中,可以看出,它使用了最简单的饿汉式单例实现方式。

public class Runtime {
    private static Runtime currentRuntime = new Runtime();

    public static Runtime getRuntime() {
        return currentRuntime;
    }

    private Runtime() {}

	// ...
    public void addShutdownHook(Thread hook) {
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            sm.checkPermission(new RuntimePermission("shutdownHooks"));
        }
        ApplicationShutdownHooks.add(hook);
    }
    // ...
}

其他模式在 JDK 中的应用汇总

在讲解理论部分时,已经讲过很多模式在 JDK 中的应用了。这里,在一起回顾下。

在讲到模板模式时,我们结合 Java Servlet、JUnit TestCase、Java InputStream、Java AbstractList 四个例子,来具体讲解了它的两个作用:扩展性和复用性。

在讲到享元模式时,我们讲到 Integer 类中的 -128~127 之间的整型对象是可以复用的,还讲到 String 类型中的常量字符串也是可以复用的。这些都是享元模式的经典应用。

在讲到职责链模式时,我们讲到 Java Servlet 中的 Filter 就是通过职责链来实现的,同时还对比了 Spring 中的 interceptor。实际上,拦截器、过滤器这些功能绝大部分都是采用职责链模式来实现的。

在讲到迭代器模式时,我们重点剖析了 Java 中 Iterator 迭代器的实现,手把手带你实现了一个针对线性数据结构的迭代器。

总结

上篇文章和本章主要剖析了 JDK 中应用到的几个设计模式,其中重点剖析的有:工厂模式、建造者模式、装饰器模式、适配器模式、模板模式、观察者模式,此外,我们还汇总了其他模式在 JDK 中的应用,比如:单例模式、享元模式、职责链模式、迭代器模式。

实际上,源码都很简单,理解起来不难,都没有逃出我们之前讲解的理论知识范畴。学习的重点也不是表面上去理解、记忆某某类用了哪种设计模式,而是让你了解,在真是的项目开发中,如何灵活应用设计模式,做到活学活用,能够根据具体的场景、需求,做灵活的设计和实现上的调整。文章来源地址https://www.toymoban.com/news/detail-860721.html

到了这里,关于设计模式学习笔记 - 开源实战一(下):通过剖析JDK源码学习灵活应用设计模式的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【开源与项目实战:开源实战】85 | 开源实战四(中):剖析Spring框架中用来支持扩展的两种设计模式

    上一节课中,我们学习了 Spring 框架背后蕴藏的一些经典设计思想,比如约定优于配置、低侵入松耦合、模块化轻量级等等。我们可以将这些设计思想借鉴到其他框架开发中,在大的设计层面提高框架的代码质量。这也是我们在专栏中讲解这部分内容的原因。 除了上一节课中

    2024年02月11日
    浏览(38)
  • 学习笔记-设计模式-创建型模式-工厂模式

    工厂模式是一种创建者设计模式,细分之下可以分成三类 简单工厂模式 , 工厂方法模式 和 抽象工厂模式 。 简单工厂模式 最简单的工厂模式,它采用静态方法的方式来决定应该应该生产什么商品。 它的优点在于 将创建实例的工作与使用实例的工作分开,使用者不必关心类

    2024年02月10日
    浏览(28)
  • 学习笔记-设计模式-创建型模式-单例模式

    一个类只有一个实例,并提供一个全局访问此实例的点,哪怕多线程同时访问。 单例模式主要解决了 一个全局使用的类被频繁的创建和消费 的问题。 单例模式的案例场景 数据库的连接池不会反复创建 spring中一个单例模式bean的生成和使用 在我们平常的代码中需要设置全局

    2024年02月08日
    浏览(41)
  • 设计模式学习笔记

    把对象的创建和使用相分离 定义工厂接口和产品接口,但如何创建实际工厂和实际产品被推迟到子类实现,从而使调用方只和抽象工厂与抽象产品打交道 调用方尽量持有接口或抽象类,避免持有具体类型的子类,以便工厂方法能随时切换不同的子类返回,却不影响调用方代

    2024年02月19日
    浏览(25)
  • 设计模式的学习笔记

    1 设计模式概述 1.1 软件设计模式的产生背景 设计模式最初并不是出现在软件设计中,而是被用于建筑领域的设计中。 1977 年美国著名建筑大师、加利福尼亚大学伯克利分校环境结构中心主任 Christopher Alexander 在他的著作《建筑模式语言:城镇、建筑、构造》中描述了一些常见

    2024年01月20日
    浏览(41)
  • 命令模式 Command Pattern 《游戏设计模式》学习笔记

    对于一般的按键输入,我们通常这么做,直接if按了什么键,就执行相应的操作 在这里我们是将用户的输入和程序行为硬编码在一起,这是我们很自然就想到的最快的做法。 但是如果这是一个大型游戏,往往我们需要实现一个按键配置的功能(话说2077直到上线都没有实现这

    2024年02月14日
    浏览(32)
  • 【设计模式——学习笔记】23种设计模式——原型模式Prototype(原理讲解+应用场景介绍+案例介绍+Java代码实现)

    原型模式指用通过拷贝原型实例创建新的实例,新实例和原型实例的属性完全一致 原型模式是一种创建型设计模式 工作原理是通过调用原型实例的 clone() 方法来完成克隆,原型实例需要实现Cloneable接口,并重写 clone() 方法 需要为每个类开发一个克隆方法,这对全新的类来说

    2024年02月16日
    浏览(36)
  • 【设计模式——学习笔记】23种设计模式——状态模式State(原理讲解+应用场景介绍+案例介绍+Java代码实现)

    请编写程序完成APP抽奖活动具体要求如下: 假如每参加一次这个活动要扣除用户50积分,中奖概率是10% 奖品数量固定,抽完就不能抽奖 活动有四个状态: 可以抽奖、不能抽奖、发放奖品和奖品领完,活动的四个状态转换关系图如下 一开始的状态为“不能抽奖”,当扣除50积分

    2024年02月12日
    浏览(34)
  • 【设计模式——学习笔记】23种设计模式——策略模式Strategy(原理讲解+应用场景介绍+案例介绍+Java代码实现)

    有各种鸭子,比如野鸭、北京鸭、水鸭等。 鸭子有各种行为,比如走路、叫、飞行等。不同鸭子的行为可能略有不同。要求显示鸭子的信息 不同的鸭子继承一个父类Duck,如果是相同的行为就继承,不同行为就重写方法 实现 【鸭子抽象类】 【野鸭】 【北京鸭】 【玩具鸭】

    2024年02月12日
    浏览(46)
  • 【设计模式——学习笔记】23种设计模式——桥接模式Bridge(原理讲解+应用场景介绍+案例介绍+Java代码实现)

    现在对不同手机类型的不同品牌实现操作编程(比如:开机、关机、上网,打电话等),如图 【对应类图】 【分析】 扩展性问题(类爆炸),如果我们再增加手机的样式(旋转式),就需要增加各个品牌手机的类,同样如果我们增加一个手机品牌,也要在各个手机样式类下增加。 违

    2024年02月15日
    浏览(32)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包