面試官:JDK中都用了哪些設(shè)計(jì)模式?
設(shè)計(jì)模式是前輩們經(jīng)過實(shí)踐驗(yàn)證總結(jié)的解決方案,幫助我們構(gòu)建出更具可維護(hù)性、可擴(kuò)展性和可讀性的代碼。當(dāng)然,在面試的過程中,也會或多或少的被問到。那么今天,我們就來看一道設(shè)計(jì)模式中的常見面試問題:JDK 中都用了哪些設(shè)計(jì)模式?
我按照大家比較熟悉且好理解的方式,把 JDK 中使用的設(shè)計(jì)模式總結(jié)了一下,如下圖所示:
那么,接下來我們一個(gè)個(gè)來看。
1.單例模式
單例模式保證一個(gè)類只有一個(gè)實(shí)例,并提供一個(gè)全局訪問點(diǎn)。
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 使用的餓漢方式實(shí)現(xiàn)了單例模式。
2.工廠模式
工廠模式提供了一種將對象創(chuàng)建的過程封裝在一個(gè)單獨(dú)的類中的方法,這個(gè)類就是工廠類。
線程池中的所有線程的創(chuàng)建都是通過工廠創(chuàng)建的,使用的就是工廠模式,具體源碼如下:
3.代理模式
代理模式是一種為其他對象提供一種代理以控制對這個(gè)對象的訪問的設(shè)計(jì)模式。代理對象在客戶端和目標(biāo)對象之間起到中介的作用,并且可以去掉客戶不能看到的內(nèi)容和服務(wù)或者添加客戶需要的額外服務(wù)。
JDK 內(nèi)置了動態(tài)代理的功能,動態(tài)代理是代理模式的一種實(shí)現(xiàn),它是由 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.目標(biāo)類(被代理類)
class RealSubject implements Subject {
@Override
public void doSomething() {
System.out.println("RealSubject is doing something");
}
}
// 3.動態(tài)代理類
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) {
// 創(chuàng)建真實(shí)對象
Subject realSubject = new RealSubject();
// 創(chuàng)建動態(tài)代理處理器
InvocationHandler handler = new DynamicProxyHandler(realSubject);
// 創(chuàng)建代理對象
Subject proxySubject = (Subject) Proxy.newProxyInstance(
realSubject.getClass().getClassLoader(),
realSubject.getClass().getInterfaces(),
handler);
// 調(diào)用代理對象的方法
proxySubject.doSomething();
}
}
4.迭代器模式
迭代器模式能夠提供一種簡單的方法來遍歷容器中的每個(gè)元素。通過迭代器,用戶可以輕松地訪問容器中所有的元素,簡化了編程過程。
Iterable 就是標(biāo)準(zhǔn)的迭代器模式,Collection 就是 Iterator 的子類,它的使用代碼如下:
import java.util.ArrayList;
import java.util.Iterator;
public class IteratorDemo {
public static void main(String[] args) {
// 創(chuàng)建一個(gè) 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)定義了一個(gè)操作中的算法骨架,將一些步驟延遲到子類中實(shí)現(xiàn)。模板方法使得子類可以在不改變算法結(jié)構(gòu)的情況下,重新定義算法中的某些步驟。
在 AQS(AbstractQueuedSynchronizer) 中,acquire 方法和 release 方法使用了模板方法模式。
這些方法之所以被認(rèn)為是模板方法模式,是因?yàn)樗鼈兌x了一個(gè)操作的基本框架或流程,但其中的某些關(guān)鍵步驟被設(shè)計(jì)為抽象方法,留給子類去具體實(shí)現(xiàn)。
以 acquire 方法為例,它大致的流程包括嘗試獲取資源、如果獲取失敗則將當(dāng)前線程加入等待隊(duì)列、阻塞線程等步驟。但是具體如何判斷能否獲取資源(通過調(diào)用 tryAcquire 方法),以及在獲取失敗后的一些處理細(xì)節(jié),是由子類去實(shí)現(xiàn)的,具體源碼如下:
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
例如,基于 AQS 實(shí)現(xiàn)的 ReentrantLock 中就重寫了 tryAcquire 方法,實(shí)現(xiàn)源碼如下:
6.裝飾器模式
裝飾器模式是在不修改原對象的基礎(chǔ)上,動態(tài)地給對象添加額外功能的設(shè)計(jì)模式。
BufferedInputStream 就是典型裝飾器模式,當(dāng)使用普通的 InputStream 讀取數(shù)據(jù)時(shí),每次可能都會進(jìn)行實(shí)際的 I/O 操作,而 BufferedInputStream 會先將一部分?jǐn)?shù)據(jù)讀入緩沖區(qū),后續(xù)的讀取操作可以直接從緩沖區(qū)獲取,減少了實(shí)際的 I/O 次數(shù)。
例如以下代碼:
InputStream inputStream = new FileInputStream("file.txt");
BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
BufferedInputStream 并沒有改變 FileInputStream 的基本結(jié)構(gòu)和接口,只是為其添加了緩沖的特性。
7.策略模式
策略模式定義了一系列可互換的算法,并將每一個(gè)算法封裝起來,使它們可以互相替換。
Comparator 是策略模式的一個(gè)典型例子,Comparator 接口定義了一個(gè)比較兩個(gè)對象的方法 compare(T o1, T o2)。這個(gè)接口允許用戶定義不同的比較策略,使得我們可以靈活地改變排序或比較邏輯。
例如以下示例代碼:
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.建造者模式
建造者模式是一種創(chuàng)建型設(shè)計(jì)模式,用于通過一系列的步驟來創(chuàng)建復(fù)雜的對象。它將對象的構(gòu)建過程與其表示相分離,使得同樣的構(gòu)建過程可以創(chuàng)建不同的表示。
在 JDK 中,使用建造者模式的常見例子是 StringBuilder 和 StringBuffer 類。
雖然這兩個(gè)類本身不是傳統(tǒng)意義上的建造者模式實(shí)現(xiàn)(因?yàn)榻ㄔ煺吣J酵ǔS糜跇?gòu)建不同的表示或者不同部分的同一個(gè)對象),它們提供了一種鏈?zhǔn)秸{(diào)用的方式來構(gòu)建和修改字符串,這在某種程度上體現(xiàn)了建造者模式的思想。
例如以下代碼:
public class StringBuilderDemo {
public static void main(String[] args) {
// 使用 StringBuilder 構(gòu)建和修改字符串
StringBuilder builder = new StringBuilder();
builder.append("Hello")
.append(", ")
.append("world")
.append("!")
.insert(7, "beautiful ")
.deleteCharAt(13);
// 輸出構(gòu)建和修改后的字符串
System.out.println(builder.toString());
// 輸出: Hello, beautiful world!
}
}
StringBuilder 通過鏈?zhǔn)秸{(diào)用 append、insert 和 deleteCharAt 方法來逐步構(gòu)建和修改字符串。這種方式使得構(gòu)建和修改字符串的過程更加流暢和易于閱讀。