設計模式常見面試題匯總
1.說一下設計模式?你都知道哪些?
答:設計模式總共有 23 種,總體來說可以分為三大類:創建型模式( Creational Patterns )、結構型模式( Structural Patterns )和行為型模式( Behavioral Patterns )。
**分類** **包含** **關注點** 創建型模式 工廠模式、抽象工廠模式、單例模式、建造者模式、原型模式 關注於對象的創建,同時隱藏創建邏輯 結構型模式 適配器模式、過濾器模式、裝飾模式、享元模式、代理模式、外觀模式、組合模式、橋接模式 關注類和對象之間的組合 行為型模式 責任鏈模式、命令模式、中介者模式、觀察者模式、狀態模式、策略模式、模板模式、空對象模式、備忘錄模式、迭代器模式、解釋器模式、訪問者模式 關注對象之間的通信
下面會對常用的設計模式分別做詳細的說明。
2.什么是單例模式?
答:單例模式是一種常用的軟件設計模式,在應用這個模式時,單例對象的類必須保證只有一個實例存在,整個系統只能使用一個對象實例。
優點:不會頻繁地創建和銷毀對象,浪費系統資源。
使用場景:IO 、數據庫連接、Redis 連接等。
單例模式代碼實現:
class Singleton {
private static Singleton instance = new Singleton();
public static Singleton getInstance() {
return instance;
}
}
單例模式調用代碼:
public class Lesson7\_3 {
public static void main(String[] args) {
Singleton singleton1 = Singleton.getInstance();
Singleton singleton2 = Singleton.getInstance();
System.out.println(singleton1 == singleton2);
}
}
程序的輸出結果:true
可以看出以上單例模式是在類加載的時候就創建了,這樣會影響程序的啟動速度,那如何實現單例模式的延遲加載?在使用時再創建?
單例延遲加載代碼:
// 單例模式-延遲加載版
class SingletonLazy {
private static SingletonLazy instance;
public static SingletonLazy getInstance() {
if (instance == null) {
instance = new SingletonLazy();
}
return instance;
}
}
以上為非線程安全的,單例模式如何支持多線程?
使用 synchronized 來保證,單例模式的線程安全代碼:
class SingletonLazy {
private static SingletonLazy instance;
public static synchronized SingletonLazy getInstance() {
if (instance == null) {
instance = new SingletonLazy();
}
return instance;
}
}
3.什么是簡單工廠模式?
答:簡單工廠模式又叫靜態工廠方法模式,就是建立一個工廠類,對實現了同一接口的一些類進行實例的創建。比如,一台咖啡機就可以理解為一個工廠模式,你只需要按下想喝的咖啡品類的按鈕(摩卡或拿鐵),它就會給你生產一杯相應的咖啡,你不需要管它內部的具體實現,只要告訴它你的需求即可。
優點:
- 工廠類含有必要的判斷邏輯,可以決定在什么時候創建哪一個產品類的實例,客戶端可以免除直接創建產品對象的責任,而僅僅“消費”產品;簡單工廠模式通過這種做法實現了對責任的分割,它提供了專門的工廠類用於創建對象;
- 客戶端無須知道所創建的具體產品類的類名,只需要知道具體產品類所對應的參數即可,對於一些復雜的類名,通過簡單工廠模式可以減少使用者的記憶量;
- 通過引入配置文件,可以在不修改任何客戶端代碼的情況下更換和增加新的具體產品類,在一定程度上提高了系統的靈活性。
缺點:
- 不易拓展,一旦添加新的產品類型,就不得不修改工廠的創建邏輯;
- 產品類型較多時,工廠的創建邏輯可能過於復雜,一旦出錯可能造成所有產品的創建失敗,不利於系統的維護。
簡單工廠示意圖如下:
簡單工廠代碼實現:
class Factory {
public static String createProduct(String product) {
String result = null;
switch (product) {
case "Mocca":
result = "摩卡";
break;
case "Latte":
result = "拿鐵";
break;
default:
result = "其他";
break;
}
return result;
}
}
4.什么是抽象工廠模式?
答:抽象工廠模式是在簡單工廠的基礎上將未來可能需要修改的代碼抽象出來,通過繼承的方式讓子類去做決定。
比如,以上面的咖啡工廠為例,某天我的口味突然變了,不想喝咖啡了想喝啤酒,這個時候如果直接修改簡單工廠里面的代碼,這種做法不但不夠優雅,也不符合軟件設計的“開閉原則”,因為每次新增品類都要修改原來的代碼。這個時候就可以使用抽象工廠類了,抽象工廠里只聲明方法,具體的實現交給子類(子工廠)去實現,這個時候再有新增品類的需求,只需要新創建代碼即可。
抽象工廠實現代碼如下:
public class AbstractFactoryTest {
public static void main(String[] args) {
// 抽象工廠
String result = (new CoffeeFactory()).createProduct("Latte");
System.out.println(result); // output:拿鐵
}
}
// 抽象工廠
abstract class AbstractFactory{
public abstract String createProduct(String product);
}
// 啤酒工廠
class BeerFactory extends AbstractFactory{
@Override
public String createProduct(String product) {
String result = null;
switch (product) {
case "Hans":
result = "漢斯";
break;
case "Yanjing":
result = "燕京";
break;
default:
result = "其他啤酒";
break;
}
return result;
}
}
/\* \* 咖啡工廠 \*/
class CoffeeFactory extends AbstractFactory{
@Override
public String createProduct(String product) {
String result = null;
switch (product) {
case "Mocca":
result = "摩卡";
break;
case "Latte":
result = "拿鐵";
break;
default:
result = "其他咖啡";
break;
}
return result;
}
}
5.什么是觀察者模式?
觀察者模式是定義對象間的一種一對多依賴關系,使得每當一個對象狀態發生改變時,其相關依賴對象皆得到通知並被自動更新。觀察者模式又叫做發布-訂閱(Publish/Subscribe)模式、模型-視圖(Model/View)模式、源-監聽器(Source/Listener)模式或從屬者(Dependents)模式。 優點:
- 觀察者模式可以實現表示層和數據邏輯層的分離,並定義了穩定的消息更新傳遞機制,抽象了更新接口,使得可以有各種各樣不同的表示層作為具體觀察者角色;
- 觀察者模式在觀察目標和觀察者之間建立一個抽象的耦合;
- 觀察者模式支持廣播通信;
- 觀察者模式符合開閉原則(對拓展開放,對修改關閉)的要求。
缺點:
- 如果一個觀察目標對象有很多直接和間接的觀察者的話,將所有的觀察者都通知到會花費很多時間;
- 如果在觀察者和觀察目標之間有循環依賴的話,觀察目標會觸發它們之間進行循環調用,可能導致系統崩潰;
- 觀察者模式沒有相應的機制讓觀察者知道所觀察的目標對象是怎么發生變化的,而僅僅只是知道觀察目標發生了變化。
在觀察者模式中有如下角色:
- Subject:抽象主題(抽象被觀察者),抽象主題角色把所有觀察者對象保存在一個集合里,每個主題都可以有任意數量的觀察者,抽象主題提供一個接口,可以增加和刪除觀察者對象;
- ConcreteSubject:具體主題(具體被觀察者),該角色將有關狀態存入具體觀察者對象,在具體主題的內部狀態發生改變時,給所有注冊過的觀察者發送通知;
- Observer:抽象觀察者,是觀察者者的抽象類,它定義了一個更新接口,使得在得到主題更改通知時更新自己;
- ConcrereObserver:具體觀察者,實現抽象觀察者定義的更新接口,以便在得到主題更改通知時更新自身的狀態。
觀察者模式實現代碼如下。
1)定義觀察者(消息接收方)
/\* \* 觀察者(消息接收方) \*/
interface Observer {
public void update(String message);
}
/\* \* 具體的觀察者(消息接收方) \*/
class ConcrereObserver implements Observer {
private String name;
public ConcrereObserver(String name) {
this.name = name;
}
@Override
public void update(String message) {
System.out.println(name + ":" + message);
}
}
2)定義被觀察者(消息發送方)
/\* \* 被觀察者(消息發布方) \*/
interface Subject {
// 增加訂閱者
public void attach(Observer observer);
// 刪除訂閱者
public void detach(Observer observer);
// 通知訂閱者更新消息
public void notify(String message);
}
/\* \* 具體被觀察者(消息發布方) \*/
class ConcreteSubject implements Subject {
// 訂閱者列表(存儲信息)
private List<Observer> list = new ArrayList<Observer>();
@Override
public void attach(Observer observer) {
list.add(observer);
}
@Override
public void detach(Observer observer) {
list.remove(observer);
}
@Override
public void notify(String message) {
for (Observer observer : list) {
observer.update(message);
}
}
}
3)代碼調用
public class ObserverTest {
public static void main(String[] args) {
// 定義發布者
ConcreteSubject concreteSubject = new ConcreteSubject();
// 定義訂閱者
ConcrereObserver concrereObserver = new ConcrereObserver("老王");
ConcrereObserver concrereObserver2 = new ConcrereObserver("Java");
// 添加訂閱
concreteSubject.attach(concrereObserver);
concreteSubject.attach(concrereObserver2);
// 發布信息
concreteSubject.notify("更新了");
}
}
程序執行結果如下:
老王:更新了
Java:更新了
6.什么是裝飾器模式?
答:裝飾器模式是指動態地給一個對象增加一些額外的功能,同時又不改變其結構。
優點:裝飾類和被裝飾類可以獨立發展,不會相互耦合,裝飾模式是繼承的一個替代模式,裝飾模式可以動態擴展一個實現類的功能。
裝飾器模式的關鍵:裝飾器中使用了被裝飾的對象。
比如,創建一個對象“laowang”,給對象添加不同的裝飾,穿上夾克、戴上帽子......,這個執行過程就是裝飾者模式,實現代碼如下。
1)定義頂層對象,定義行為
interface IPerson {
void show();
}
2)定義裝飾器超類
class DecoratorBase implements IPerson{
IPerson iPerson;
public DecoratorBase(IPerson iPerson){
this.iPerson = iPerson;
}
@Override
public void show() {
iPerson.show();
}
}
3)定義具體裝飾器
class Jacket extends DecoratorBase {
public Jacket(IPerson iPerson) {
super(iPerson);
}
@Override
public void show() {
// 執行已有功能
iPerson.show();
// 定義新行為
System.out.println("穿上夾克");
}
}
class Hat extends DecoratorBase {
public Hat(IPerson iPerson) {
super(iPerson);
}
@Override
public void show() {
// 執行已有功能
iPerson.show();
// 定義新行為
System.out.println("戴上帽子");
}
}
4)定義具體對象
class LaoWang implements IPerson{
@Override
public void show() {
System.out.println("什么都沒穿");
}
}
5)裝飾器模式調用
public class DecoratorTest {
public static void main(String[] args) {
LaoWang laoWang = new LaoWang();
Jacket jacket = new Jacket(laoWang);
Hat hat = new Hat(jacket);
hat.show();
}
}
7.什么是模板方法模式?
答:模板方法模式是指定義一個模板結構,將具體內容延遲到子類去實現。
優點:
- 提高代碼復用性:將相同部分的代碼放在抽象的父類中,而將不同的代碼放入不同的子類中;
- 實現了反向控制:通過一個父類調用其子類的操作,通過對子類的具體實現擴展不同的行為,實現了反向控制並且符合開閉原則。
以給冰箱中放水果為例,比如,我要放一個香蕉:開冰箱門 → 放香蕉 → 關冰箱門;如果我再要放一個蘋果:開冰箱門 → 放蘋果 → 關冰箱門。可以看出它們之間的行為模式都是一樣的,只是存放的水果品類不同而已,這個時候就非常適用模板方法模式來解決這個問題,實現代碼如下:
/\* \* 添加模板方法 \*/
abstract class Refrigerator {
public void open() {
System.out.println("開冰箱門");
}
public abstract void put();
public void close() {
System.out.println("關冰箱門");
}
}
class Banana extends Refrigerator {
@Override
public void put() {
System.out.println("放香蕉");
}
}
class Apple extends Refrigerator {
@Override
public void put() {
System.out.println("放蘋果");
}
}
/\* \* 調用模板方法 \*/
public class TemplateTest {
public static void main(String[] args) {
Refrigerator refrigerator = new Banana();
refrigerator.open();
refrigerator.put();
refrigerator.close();
}
}
程序執行結果:
開冰箱門
放香蕉
關冰箱門
8.什么是代理模式?
代理模式是給某一個對象提供一個代理,並由代理對象控制對原對象的引用。
優點:
- 代理模式能夠協調調用者和被調用者,在一定程度上降低了系統的耦合度;
- 可以靈活地隱藏被代理對象的部分功能和服務,也增加額外的功能和服務。
缺點:
- 由於使用了代理模式,因此程序的性能沒有直接調用性能高;
- 使用代理模式提高了代碼的復雜度。
舉一個生活中的例子:比如買飛機票,由於離飛機場太遠,直接去飛機場買票不太現實,這個時候我們就可以上攜程 App 上購買飛機票,這個時候攜程 App 就相當於是飛機票的代理商。
代理模式實現代碼如下:
/\* \* 定義售票接口 \*/
interface IAirTicket {
void buy();
}
/\* \* 定義飛機場售票 \*/
class AirTicket implements IAirTicket {
@Override
public void buy() {
System.out.println("買票");
}
}
/\* \* 代理售票平台 \*/
class ProxyAirTicket implements IAirTicket {
private AirTicket airTicket;
public ProxyAirTicket() {
airTicket = new AirTicket();
}
@Override
public void buy() {
airTicket.buy();
}
}
/\* \* 代理模式調用 \*/
public class ProxyTest {
public static void main(String[] args) {
IAirTicket airTicket = new ProxyAirTicket();
airTicket.buy();
}
}
9.什么是策略模式?
答:策略模式是指定義一系列算法,將每個算法都封裝起來,並且使他們之間可以相互替換。
優點:遵循了開閉原則,擴展性良好。
缺點:隨着策略的增加,對外暴露越來越多。
以生活中的例子來說,比如我們要出去旅游,選擇性很多,可以選擇騎車、開車、坐飛機、坐火車等,就可以使用策略模式,把每種出行作為一種策略封裝起來,后面增加了新的交通方式了,如超級高鐵、火箭等,就可以不需要改動原有的類,新增交通方式即可,這樣也符合軟件開發的開閉原則。 策略模式實現代碼如下:
/\* \* 聲明旅行 \*/
interface ITrip {
void going();
}
class Bike implements ITrip {
@Override
public void going() {
System.out.println("騎自行車");
}
}
class Drive implements ITrip {
@Override
public void going() {
System.out.println("開車");
}
}
/\* \* 定義出行類 \*/
class Trip {
private ITrip trip;
public Trip(ITrip trip) {
this.trip = trip;
}
public void doTrip() {
this.trip.going();
}
}
/\* \* 執行方法 \*/
public class StrategyTest {
public static void main(String[] args) {
Trip trip = new Trip(new Bike());
trip.doTrip();
}
}
程序執行的結果:
騎自行車
10.什么是適配器模式?
答:適配器模式是將一個類的接口變成客戶端所期望的另一種接口,從而使原本因接口不匹配而無法一起工作的兩個類能夠在一起工作。
優點:
- 可以讓兩個沒有關聯的類一起運行,起着中間轉換的作用;
- 靈活性好,不會破壞原有的系統。
缺點:過多地使用適配器,容易使代碼結構混亂,如明明看到調用的是 A 接口,內部調用的卻是 B 接口的實現。
以生活中的例子來說,比如有一個充電器是 MicroUSB 接口,而手機充電口卻是 TypeC 的,這個時候就需要一個把 MicroUSB 轉換成 TypeC 的適配器,如下圖所示:
適配器實現代碼如下:
/\* \* 傳統的充電線 MicroUSB \*/
interface MicroUSB {
void charger();
}
/\* \* TypeC 充電口 \*/
interface ITypeC {
void charger();
}
class TypeC implements ITypeC {
@Override
public void charger() {
System.out.println("TypeC 充電");
}
}
/\* \* 適配器 \*/
class AdapterMicroUSB implements MicroUSB {
private TypeC typeC;
public AdapterMicroUSB(TypeC typeC) {
this.typeC = typeC;
}
@Override
public void charger() {
typeC.charger();
}
}
/\* \* 測試調用 \*/
public class AdapterTest {
public static void main(String[] args) {
TypeC typeC = new TypeC();
MicroUSB microUSB = new AdapterMicroUSB(typeC);
microUSB.charger();
}
}
程序執行結果:
TypeC 充電
11.JDK 類庫常用的設計模式有哪些?
答:JDK 常用的設計模式如下:
1)工廠模式
java.text.DateFormat 工具類,它用於格式化一個本地日期或者時間。
public final static DateFormat getDateInstance();
public final static DateFormat getDateInstance(int style);
public final static DateFormat getDateInstance(int style,Locale locale);
加密類
KeyGenerator keyGenerator = KeyGenerator.getInstance("DESede");
Cipher cipher = Cipher.getInstance("DESede");
2)適配器模式
把其他類適配為集合類
List<Integer> arrayList = java.util.Arrays.asList(new Integer[]{1,2,3});
List<Integer> arrayList = java.util.Arrays.asList(1,2,3);
3)代理模式
如 JDK 本身的動態代理。
interface Animal {
void eat();
}
class Dog implements Animal {
@Override
public void eat() {
System.out.println("The dog is eating");
}
}
class Cat implements Animal {
@Override
public void eat() {
System.out.println("The cat is eating");
}
}
// JDK 代理類
class AnimalProxy implements InvocationHandler {
private Object target; // 代理對象
public Object getInstance(Object target) {
this.target = target;
// 取得代理對象
return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("調用前");
Object result = method.invoke(target, args); // 方法調用
System.out.println("調用后");
return result;
}
}
public static void main(String[] args) {
// JDK 動態代理調用
AnimalProxy proxy = new AnimalProxy();
Animal dogProxy = (Animal) proxy.getInstance(new Dog());
dogProxy.eat();
}
4)單例模式
全局只允許有一個實例,比如:
Runtime.getRuntime();
5)裝飾器
為一個對象動態的加上一系列的動作,而不需要因為這些動作的不同而產生大量的繼承類。
java.io.BufferedInputStream(InputStream);
java.io.DataInputStream(InputStream);
java.io.BufferedOutputStream(OutputStream);
java.util.zip.ZipOutputStream(OutputStream);
java.util.Collections.checkedList(List list, Class type) ;
6)模板方法模式
定義一個操作中算法的骨架,將一些步驟的執行延遲到其子類中。
比如,Arrays.sort() 方法,它要求對象實現 Comparable 接口。
class Person implements Comparable{
private Integer age;
public Person(Integer age){
this.age = age;
}
@Override
public int compareTo(Object o) {
Person person = (Person)o;
return this.age.compareTo(person.age);
}
}
public class SortTest(){
public static void main(String[] args){
Person p1 = new Person(10);
Person p2 = new Person(5);
Person p3 = new Person(15);
Person[] persons = {p1,p2,p3};
//排序
Arrays.sort(persons);
}
}
12.IO 使用了什么設計模式?
答:IO 使用了適配器模式和裝飾器模式。
- 適配器模式:由於 InputStream 是字節流不能享受到字符流讀取字符那么便捷的功能,借助 InputStreamReader 將其轉為 Reader 子類,因而可以擁有便捷操作文本文件方法;
- 裝飾器模式:將 InputStream 字節流包裝為其他流的過程就是裝飾器模式,比如,包裝為 FileInputStream、ByteArrayInputStream、PipedInputStream 等。
13.Spring 中都使用了哪些設計模式?
答:Spring 框架使用的設計模式如下。
- 代理模式:在 AOP 中有使用
- 單例模式:bean 默認是單例模式
- 模板方法模式:jdbcTemplate
- 工廠模式:BeanFactory
- 觀察者模式:Spring 事件驅動模型就是觀察者模式很經典的一個應用,比如,ContextStartedEvent 就是 ApplicationContext 啟動后觸發的事件
- 適配器模式:Spring MVC 中也是用到了適配器模式適配 Controller
還沒有評論
評論