设计模式是前辈们经过实践验证总结的解决方案,帮助我们构建出更具可维护性、可扩展性和可读性的代码。当然,在面试的过程中,也会或多或少的被问到。那么今天,我们就来看一道设计模式中的常见面试问题:JDK 中都用了哪些设计模式?

我按照大家比较熟悉且好理解的方式,把 JDK 中使用的设计模式总结了一下,如下图所示:

那么,接下来我们一个个来看。

1.单例模式

单例模式保证一个类只有一个实例,并提供一个全局访问点。

Runtime 类使用了单例模式,如下源码可知:

public class Runtime {
    private static final Runtime currentRuntime = new Runtime();
    private static Version version;
    /**
     * Returns the runtime object associated with the current Java application.
     * Most of the methods of class {@code Runtime} are instance
     * methods and must be invoked with respect to the current runtime object.
     *
     * @return  the {@code Runtime} object associated with the current
     *          Java application.
     */
    public static Runtime getRuntime() {
        return currentRuntime;
    }
    /** Don't let anyone else instantiate this class */
    private Runtime() {}
    // 省略其他源码
}

从以上源码可以看出,Runtime 使用的饿汉方式实现了单例模式。

2.工厂模式

工厂模式提供了一种将对象创建的过程封装在一个单独的类中的方法,这个类就是工厂类。

线程池中的所有线程的创建都是通过工厂创建的,使用的就是工厂模式,具体源码如下:

3.代理模式

代理模式是一种为其他对象提供一种代理以控制对这个对象的访问的设计模式。代理对象在客户端和目标对象之间起到中介的作用,并且可以去掉客户不能看到的内容和服务或者添加客户需要的额外服务。

JDK 内置了动态代理的功能,动态代理是代理模式的一种实现,它是由 java.lang.reflect.Proxy 类提供的。

Proxy 使用 Demo 如下:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

// 1.接口
interface Subject {
    void doSomething();
}

// 2.目标类(被代理类)
class RealSubject implements Subject {
    @Override
    public void doSomething() {
        System.out.println("RealSubject is doing something");
    }
}

// 3.动态代理类
class DynamicProxyHandler implements InvocationHandler {
    private Object target;
    DynamicProxyHandler(Object target) {
        this.target = target;
    }
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("Before calling method");
        Object result = method.invoke(target, args);
        System.out.println("After calling method");
        return result;
    }
}

public class JDKProxyDemo {
    public static void main(String[] args) {
        // 创建真实对象
        Subject realSubject = new RealSubject();
        // 创建动态代理处理器
        InvocationHandler handler = new DynamicProxyHandler(realSubject);
        // 创建代理对象
        Subject proxySubject = (Subject) Proxy.newProxyInstance(
            realSubject.getClass().getClassLoader(),
            realSubject.getClass().getInterfaces(),
            handler);
        // 调用代理对象的方法
        proxySubject.doSomething();
    }
}

4.迭代器模式

迭代器模式能够提供一种简单的方法来遍历容器中的每个元素。通过迭代器,用户可以轻松地访问容器中所有的元素,简化了编程过程。

Iterable 就是标准的迭代器模式,Collection 就是 Iterator 的子类,它的使用代码如下:

import java.util.ArrayList;
import java.util.Iterator;

public class IteratorDemo {
    public static void main(String[] args) {
        // 创建一个 ArrayList 并添加元素
        ArrayList<String> list = new ArrayList<>();
        list.add("Apple");
        list.add("Banana");
        list.add("Orange");

        // 获取迭代器
        Iterator<String> iterator = list.iterator();

        // 使用迭代器遍历集合
        while (iterator.hasNext()) {
            String fruit = iterator.next();
            System.out.println("Fruit: " + fruit);
        }
    }
}

5.模版方法模式

模板方法模式(Template Method Pattern)定义了一个操作中的算法骨架,将一些步骤延迟到子类中实现。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。

在 AQS(AbstractQueuedSynchronizer) 中,acquire 方法和 release 方法使用了模板方法模式。

这些方法之所以被认为是模板方法模式,是因为它们定义了一个操作的基本框架或流程,但其中的某些关键步骤被设计为抽象方法,留给子类去具体实现。

以 acquire 方法为例,它大致的流程包括尝试获取资源、如果获取失败则将当前线程加入等待队列、阻塞线程等步骤。但是具体如何判断能否获取资源(通过调用 tryAcquire 方法),以及在获取失败后的一些处理细节,是由子类去实现的,具体源码如下:

protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}

例如,基于 AQS 实现的 ReentrantLock 中就重写了 tryAcquire 方法,实现源码如下:

6.装饰器模式

装饰器模式是在不修改原对象的基础上,动态地给对象添加额外功能的设计模式。

BufferedInputStream 就是典型装饰器模式,当使用普通的 InputStream 读取数据时,每次可能都会进行实际的 I/O 操作,而 BufferedInputStream 会先将一部分数据读入缓冲区,后续的读取操作可以直接从缓冲区获取,减少了实际的 I/O 次数。

例如以下代码:

InputStream inputStream = new FileInputStream("file.txt");
BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);

BufferedInputStream 并没有改变 FileInputStream 的基本结构和接口,只是为其添加了缓冲的特性

7.策略模式

策略模式定义了一系列可互换的算法,并将每一个算法封装起来,使它们可以互相替换。

Comparator 是策略模式的一个典型例子,Comparator 接口定义了一个比较两个对象的方法 compare(T o1, T o2)。这个接口允许用户定义不同的比较策略,使得我们可以灵活地改变排序或比较逻辑。

例如以下示例代码:

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;

public class StrategyPatternExample {
    static class Person {
        private String name;
        private int age;
        // 忽略 Setter、Getter 等方法
    }
    // 按照年龄升序排列
    static class AgeComparator implements Comparator<Person> {
        @Override
        public int compare(Person p1, Person p2) {
            return Integer.compare(p1.getAge(), p2.getAge());
        }
    }
    // 按照姓名降序排列
    static class NameDescendingComparator implements Comparator<Person> {
        @Override
        public int compare(Person p1, Person p2) {
            return p2.getName().compareTo(p1.getName());
        }
    }
    public static void main(String[] args) {
        ArrayList<Person> people = new ArrayList<>();
        people.add(new Person("Alice", 30));
        people.add(new Person("Bob", 25));
        people.add(new Person("Charlie", 35));

        // 使用年龄升序的策略
        Collections.sort(people, new AgeComparator());

        // 使用姓名降序的策略
        Collections.sort(people, new NameDescendingComparator());
    }
}

8.建造者模式

建造者模式是一种创建型设计模式,用于通过一系列的步骤来创建复杂的对象。它将对象的构建过程与其表示相分离,使得同样的构建过程可以创建不同的表示。

在 JDK 中,使用建造者模式的常见例子是 StringBuilder 和 StringBuffer 类。

虽然这两个类本身不是传统意义上的建造者模式实现(因为建造者模式通常用于构建不同的表示或者不同部分的同一个对象),它们提供了一种链式调用的方式来构建和修改字符串,这在某种程度上体现了建造者模式的思想。

例如以下代码:

public class StringBuilderDemo {  
    public static void main(String[] args) {  
        // 使用 StringBuilder 构建和修改字符串  
        StringBuilder builder = new StringBuilder();  
        builder.append("Hello")  
        .append(", ")  
        .append("world")  
        .append("!")  
        .insert(7, "beautiful ")  
        .deleteCharAt(13);  

        // 输出构建和修改后的字符串  
        System.out.println(builder.toString());  
        // 输出: Hello, beautiful world!  
    }  
}

StringBuilder 通过链式调用 append、insert 和 deleteCharAt 方法来逐步构建和修改字符串。这种方式使得构建和修改字符串的过程更加流畅和易于阅读。

课后思考

Spring 中都用了哪些设计模式?

特殊说明

以上内容来自我的《Java 面试突击训练营》,这门课程是有着十几年工作经验(前 360 开发工程师),10 年面试官经验的我,花费 4 年时间打磨完成的一门视频面试课。学完训练营的课程之后,基本可以应对目前市面上绝大部分公司的面试了,并且课程配备了 9 大就业服务,帮助上千人找到 Java 工作,其中上百人拿到大厂 Offer,学员最高薪资 70W 年薪,面试课目录和 9 大服务如下:

加我微信咨询:vipStone【备注:训练营】