設計模式常見面試真題詳解

文章目錄

    • 1. 設計模式
        • 1.1 說一說設計模式的六大原則
        • 1.2 說一下六大原則中的開閉原則
        • 1.3 手寫一個單例模式
        • 1.4 手寫一個線程安全的單例模式
        • 1.5 說一說你對工廠模式的理解
        • 1.6 簡單工廠模式和抽象工廠模式有什么區別?
        • 1.7 如何實現工廠模式?
        • 1.8 說一說你策略模式的理解
        • 1.9 說一說你對觀察者模式的了解
        • 1.10 說一說你對責任鏈模式的了解
        • 1.11 說一說裝飾器模式和適配器模式的區別
        • 1.12 Spring框架中用到了哪些設計模式?

1. 設計模式

1.1 說一說設計模式的六大原則

參考答案

單一職責原則

一個類,應當只有一個引起它變化的原因;即一個類應該只有一個職責。

就一個類而言,應該只專注于做一件事和僅有一個引起變化的原因,這就是所謂的單一職責原則。該原則提出了對對象職責的一種理想期望,對象不應該承擔太多職責,正如人不應該一心分為二用。唯有專注,才能保證對象的高內聚;唯有單一,才能保證對象的細粒度。對象的高內聚與細粒度有利于對象的重用。一個龐大的對象承擔了太多的職責,當客戶端需要該對象的某一個職責時,就不得不將所有的職責都包含進來,從而造成冗余代碼。

里氏替換原則

在面向對象的語言中,繼承是必不可少的、優秀的語言機制,它主要有以下幾個優點:

  • 代碼共享,減少創建類的工作量,每個子類都擁有父類的方法和屬性;
  • 提高代碼的可重用性;
  • 提高代碼的可擴展性;
  • 提高產品或項目的開放性。

相應的,繼承也存在缺點,主要體現在以下幾個方面:

  • 繼承是入侵式的。只要繼承,就必須擁有父類的所有屬性和方法;
  • 降低代碼的靈活性。子類必須擁有父類的屬性和方法,使子類受到限制;
  • 增強了耦合性。當父類的常量、變量和方法修改時,必須考慮子類的修改,這種修改可能造成大片的代碼需要重構。

從整體上看,繼承的“利”大于“弊”,然而如何讓繼承中“利”的因素發揮最大作用,同時減少“弊”所帶來的麻煩,這就需要引入“里氏替換原則”。里氏替換原則的定義有以下兩種:

  • 如果對一個類型為S的對象o1,都有類型為T的對象o2,使得以S定義的所有程序P在所有的對象o1都代換成o2時,程序P的行為沒有發生變化,那么類型T是類型S的子類型。
  • 所有引用基類的地方必須能透明地使用其子類對象。清晰明確地說明只要父類能出現的地方子類就可以出現,而且替換為子類也不會產生任何錯誤或異常,使用者可能根本就不需要知道父類還是子類;但是反過來則不可以,有子類的地方,父類未必就能適應。

依賴倒置原則

依賴倒置原則包括三種含義:

  • 高層模塊不應該依賴低層模塊,兩者都依賴其抽象;
  • 抽象不依賴細節;
  • 細節應該依賴于抽象。

傳統的過程性系統的設計辦法傾向于高層次的模塊依賴于低層次的模塊;抽象層次依賴于具體層次。“倒置”原則將這個錯誤的依賴關系倒置了過來,如下圖所示,由此命名為“依賴倒置原則”。

img

在Java語言中,抽象就是指接口或抽象類,兩者都是不能直接被實例化的;細節就是具體的實現類,實現類實現了接口或繼承了抽象類,其特點是可以直接被實例化。依賴倒置原則在Java語言中的表現是:

  • 模塊間的依賴通過抽象發生,實現類之間不發生直接的依賴關系,其依賴關系是通過接口或抽象類產生;
  • 接口或抽象類不依賴于實現類;
  • 實現類依賴于接口或抽象類。

依賴倒置原則更加精確的定義就是“面向接口編程”——OOD(Object-Oriented Design)的精髓之一。依賴倒置原則可以減少類間的耦合性,提高系統的穩定性,降低并行開發引起的風險,提高代碼的可讀性和可維護性。依賴倒置原則是JavaBean、EJB和COM等組件設計模型背后的基本原則。

接口隔離原則

接口隔離原則有如下兩種定義:

  1. 客戶端不應該依賴它不需要的接口。
  2. 類間的依賴關系應該建立在最小的接口上。

接口隔離原則的具體含義如下:

  • 一個類對另外一個類的依賴性應當是建立在最小的接口上的。
  • 一個接口代表一個角色,不應當將不同的角色都交給一個接口。沒有關系的接口合并在一起,形成一個臃腫的大接口,這是對角色和接口的污染。因此使用多個專門的接口比使用單一的總接口要好。
  • 不應該強迫客戶依賴于它們不用的方法。接口屬于客戶,不屬于它所在的類層次結構,即不要強迫客戶使用它們不用的方法,否則這些客戶就會面臨由于這些不使用的方法的改變所帶來的改變。

迪米特法則

迪米特法則又叫最少知識原則,意思是一個對象應當對其他對象盡可能少的了解。迪米特法則不同于其他的OO設計原則,它具有很多種表述方式,其中具有代表性的是以下幾種表述:

  • 只與你直接的朋友們通信;
  • 不要跟“陌生人”說話;
  • 每一個軟件單位對其他的單位都只有最少的了解,這些了解僅局限于那些與本單位密切相關的軟件單位。

按照迪米特法則,如果兩個類不必彼此直接通信,那么這兩個類就不應當發生直接的相互作用;如果一個類需要調用另一個類的某一個方法,可以通過第三者轉發這個調用。

開閉原則

開閉原則的定義是:一個軟件實體應當對擴展開放,對修改關閉。這個原則說的是,在設計一個模塊的時候,應當使這個模塊可以在不被修改的前提下被擴展,即應當可以在不必修改源代碼的情況下改變這個模塊的行為。

在面向對象的編程中,開閉原則是最基礎的原則,起到總的指導作用,其他原則(單一職責、里氏替換、依賴倒置、接口隔離、迪米特法則)都是開閉原則的具體形態,即其他原則都是開閉原則的手段和工具。開閉原則的重要性可以通過以下幾個方面來體現。

  • 開閉原則提高復用性。在面向對象的設計中,所有的邏輯都是從原子邏輯組合而來的,而不是在一個類中獨立實現一個業務邏輯,代碼粒度越小,被復用的可能性就越大,避免相同的邏輯重復增加。開閉原則的設計保證系統是一個在高層次上實現了復用的系統。
  • 開閉原則提高可維護性。一個軟件投產后,維護人員的工作不僅僅是對數據進行維護,還可能對程序進行擴展,就是擴展一個類,而不是修改一個類。開閉原則對已有軟件模塊,特別是最重要的抽象層模塊要求不能再修改,這就使變化中的軟件系統有一定的穩定性和延續性,便于系統的維護。
  • 開閉原則提高靈活性。所有的軟件系統都有一個共同的性質,即對系統的需求都會隨時間的推移而發生變化。在軟件系統面臨新的需求時,系統的設計必須是穩定的。開閉原則可以通過擴展已有的軟件系統,提供新的行為,能快速應對變化,以滿足對軟件新的需求,使變化中的軟件系統有一定的適應性和靈活性。
  • 開閉原則易于測試。測試是軟件開發過程中必不可少的一個環節。測試代碼不僅要保證邏輯的正確性,還要保證苛刻條件(高壓力、異常、錯誤)下不產生“有毒代碼”(Poisonous Code),因此當有變化提出時,原有健壯的代碼要盡量不修改,而是通過擴展來實現。否則,就需要把原有的測試過程回籠一遍,需要進行單元測試、功能測試、集成測試,甚至是驗收測試。開閉原則的使用,保證軟件是通過擴展來實現業務邏輯的變化,而不是修改。因此,對于新增加的類,只需新增相應的測試類,編寫對應的測試方法,只要保證新增的類是正確的就可以了。

1.2 說一下六大原則中的開閉原則

參考答案

開閉原則的定義是:一個軟件實體應當對擴展開放,對修改關閉。這個原則說的是,在設計一個模塊的時候,應當使這個模塊可以在不被修改的前提下被擴展,即應當可以在不必修改源代碼的情況下改變這個模塊的行為。

在面向對象的編程中,開閉原則是最基礎的原則,起到總的指導作用,其他原則(單一職責、里氏替換、依賴倒置、接口隔離、迪米特法則)都是開閉原則的具體形態,即其他原則都是開閉原則的手段和工具。開閉原則的重要性可以通過以下幾個方面來體現。

  • 開閉原則提高復用性。在面向對象的設計中,所有的邏輯都是從原子邏輯組合而來的,而不是在一個類中獨立實現一個業務邏輯,代碼粒度越小,被復用的可能性就越大,避免相同的邏輯重復增加。開閉原則的設計保證系統是一個在高層次上實現了復用的系統。
  • 開閉原則提高可維護性。一個軟件投產后,維護人員的工作不僅僅是對數據進行維護,還可能對程序進行擴展,就是擴展一個類,而不是修改一個類。開閉原則對已有軟件模塊,特別是最重要的抽象層模塊要求不能再修改,這就使變化中的軟件系統有一定的穩定性和延續性,便于系統的維護。
  • 開閉原則提高靈活性。所有的軟件系統都有一個共同的性質,即對系統的需求都會隨時間的推移而發生變化。在軟件系統面臨新的需求時,系統的設計必須是穩定的。開閉原則可以通過擴展已有的軟件系統,提供新的行為,能快速應對變化,以滿足對軟件新的需求,使變化中的軟件系統有一定的適應性和靈活性。
  • 開閉原則易于測試。測試是軟件開發過程中必不可少的一個環節。測試代碼不僅要保證邏輯的正確性,還要保證苛刻條件(高壓力、異常、錯誤)下不產生“有毒代碼”(Poisonous Code),因此當有變化提出時,原有健壯的代碼要盡量不修改,而是通過擴展來實現。否則,就需要把原有的測試過程回籠一遍,需要進行單元測試、功能測試、集成測試,甚至是驗收測試。開閉原則的使用,保證軟件是通過擴展來實現業務邏輯的變化,而不是修改。因此,對于新增加的類,只需新增相應的測試類,編寫對應的測試方法,只要保證新增的類是正確的就可以了。

1.3 手寫一個單例模式

參考答案

餓漢式單例模式:

public class Singleton {private static Singleton instance = new Singleton();// 私有構造方法,保證外界無法直接實例化。private Singleton() {}// 通過公有的靜態方法獲取對象實例public static Singleton getInstace() {return instance;}
}

懶漢式單例模式:

public class Singleton {private static Singleton instance = null;// 私有構造方法,保證外界無法直接實例化。private Singleton() {}// 通過公有的靜態方法獲取對象實例public static Singleton getInstace() {if (instance == null) {instance = new Singleton();}return instance;}
}

1.4 手寫一個線程安全的單例模式

參考答案

在懶漢式單例模式基礎上實現線程同步:

public class Singleton {private static Singleton instance = null;// 私有構造方法,保證外界無法直接實例化。private Singleton() {}// 通過公有的靜態方法獲取對象實例synchronized public static Singleton getInstace() {if (instance == null) {instance = new Singleton();}return instance;}
}

上述代碼對靜態方法 getInstance()進行同步,以確保多線程環境下只創建一個實例。如果getInstance()方法未被同步,并且線程A和線程B同時調用此方法,則執行if (instance == null)語句時都為真,那么線程A和線程B都會創建一個對象,在內存中就會出現兩個對象,這樣就違反了單例模式。而使用synchronized關鍵字進行同步后,則不會出現此種情況。

1.5 說一說你對工廠模式的理解

參考答案

工廠模式的用意是定義一個創建產品對象的工廠接口,將實際創建性工作推遲到子類中。工廠模式可分為簡單工廠、工廠方法和抽象工廠模式。注意,我們常說的23種經典設計模式,包含了工程方法模式和抽象工廠模式,而并未包含簡單工廠模式。另外,我們平時說的工廠模式,一般默認是指工廠方法模式。

簡單工廠

簡單工廠模式其實并不算是一種設計模式,更多的時候是一種編程習慣。簡單工廠的實現思路是,定義一個工廠類,根據傳入的參數不同返回不同的實例,被創建的實例具有共同的父類或接口。簡單工廠的適用場景是:

  • 需要創建的對象較少。
  • 客戶端不關心對象的創建過程。

示例:

創建一個可以繪制不同形狀的繪圖工具,可以繪制圓形,正方形,三角形,每個圖形都會有一個draw()方法用于繪圖,不看代碼先考慮一下如何通過該模式設計完成此功能。

由題可知圓形,正方形,三角形都屬于一種圖形,并且都具有draw方法,所以首先可以定義一個接口或者抽象類,作為這三個圖像的公共父類,并在其中聲明一個公共的draw方法:

public interface Shape {void draw();
}

下面就是編寫具體的圖形,每種圖形都實現Shape接口:

// 圓形
class CircleShape implements Shape {public CircleShape() {System.out.println("CircleShape: created");}@Overridepublic void draw() {System.out.println("draw: CircleShape");}
}
// 正方形
class RectShape implements Shape {public RectShape() {System.out.println("RectShape: created");}@Overridepublic void draw() {System.out.println("draw: RectShape");}}
// 三角形
public class TriangleShape implements Shape {public TriangleShape() {System.out.println("TriangleShape: created");}@Overridepublic void draw() {System.out.println("draw: TriangleShape");}
}

下面是工廠類的具體實現:

 class ShapeFactory {public static Shape getShape(String type) {Shape shape = null;if (type.equalsIgnoreCase("circle")) {shape = new CircleShape();} else if (type.equalsIgnoreCase("rect")) {shape = new RectShape();} else if (type.equalsIgnoreCase("triangle")) {shape = new TriangleShape();}return shape;}}

為工廠類傳入不同的type可以new不同的形狀,返回結果為Shape 類型,這個就是簡單工廠核心的地方了。

工廠方法

工廠方法模式是簡單工廠的僅一步深化, 在工廠方法模式中,我們不再提供一個統一的工廠類來創建所有的對象,而是針對不同的對象提供不同的工廠。也就是說每個對象都有一個與之對應的工廠。

工廠方法的實現思路是,定義一個用于創建對象的接口,讓子類決定將哪一個類實例化。工廠方法模式讓一個類的實例化延遲到其子類。

示例:

現在需要設計一個這樣的圖片加載類,它具有多個圖片加載器,用來加載jpg,png,gif格式的圖片,每個加載器都有一個read()方法,用于讀取圖片。下面我們完成這個圖片加載類。

首先完成圖片加載器的設計,編寫一個加載器的公共接口:

public interface Reader {void read();
}

然后完成各個圖片加載器的代碼:

// jpg圖片加載器
class JpgReader implements Reader {@Overridepublic void read() {System.out.print("read jpg");}
}
// png圖片加載器
class PngReader implements Reader {@Overridepublic void read() {System.out.print("read png");}
}
// gif圖片加載器
class GifReader implements Reader {@Overridepublic void read() {System.out.print("read gif");}
}

現在我們按照定義所說定義一個抽象的工廠接口ReaderFactory:

interface ReaderFactory {Reader getReader();
}

里面有一個getReader()方法返回我們的Reader 類,接下來我們把上面定義好的每個圖片加載器都提供一個工廠類,這些工廠類實現了ReaderFactory 。

// jpg加載器工廠
class JpgReaderFactory implements ReaderFactory {@Overridepublic Reader getReader() {return new JpgReader();}
}
// png加載器工廠
class PngReaderFactory implements ReaderFactory {@Overridepublic Reader getReader() {return new PngReader();}
}
// gif加載器工廠
class GifReaderFactory implements ReaderFactory {@Overridepublic Reader getReader() {return new GifReader();}
}

在每個工廠類中我們都通過重寫的getReader()方法返回各自的圖片加載器對象。

和簡單工廠對比一下,最根本的區別在于,簡單工廠只有一個統一的工廠類,而工廠方法是針對每個要創建的對象都會提供一個工廠類,這些工廠類都實現了一個工廠基類。

下面總結一下工廠方法的適用場景:

  • 客戶端不需要知道它所創建的對象的類。
  • 客戶端可以通過子類來指定創建對應的對象。

抽象工廠

這個模式最不好理解,而且在實際應用中局限性也蠻大的,因為這個模式并不符合開閉原則。實際開發還需要做好權衡。抽象工廠模式是工廠方法的僅一步深化,在這個模式中的工廠類不單單可以創建一個對象,而是可以創建一組對象。這是和工廠方法最大的不同點。

抽象工廠的實現思路是,提供一個創建一系列相關或相互依賴對象的接口,而無須指定它們具體的類。抽象工廠和工廠方法一樣可以劃分為4大部分:

  • AbstractFactory(抽象工廠):聲明了一組用于創建對象的方法,注意是一組。
  • ConcreteFactory(具體工廠):它實現了在抽象工廠中聲明的創建對象的方法,生成一組具體對象。
  • AbstractProduct(抽象產品):它為每種對象聲明接口,在其中聲明了對象所具有的業務方法。
  • ConcreteProduct(具體產品):它定義具體工廠生產的具體對象。

示例:

現在需要做一款跨平臺的游戲,需要兼容Android,Ios,Wp三個移動操作系統,該游戲針對每個系統都設計了一套操作控制器(OperationController)和界面控制器(UIController),下面通過抽閑工廠方式完成這款游戲的架構設計。

由題可知,游戲里邊的各個平臺的UIController和OperationController應該是我們最終生產的具體產品。所以新建兩個抽象產品接口。

抽象操作控制器:

interface OperationController {void control();
}

抽象界面控制器:

interface UIController {void display();
}

然后完成各個系統平臺的具體操作控制器和界面控制器。

Android:

class AndroidOperationController implements OperationController {@Overridepublic void control() {System.out.println("AndroidOperationController");}
}
class AndroidUIController implements UIController {@Overridepublic void display() {System.out.println("AndroidInterfaceController");}
}

IOS:

class IosOperationController implements OperationController {@Overridepublic void control() {System.out.println("IosOperationController");}
}
class IosUIController implements UIController {@Overridepublic void display() {System.out.println("IosInterfaceController");}
}

WP:

class WpOperationController implements OperationController {@Overridepublic void control() {System.out.println("WpOperationController");}
}
class WpUIController implements UIController {@Overridepublic void display() {System.out.println("WpInterfaceController");}
}

下面定義一個抽閑工廠,該工廠需要可以創建OperationController和UIController。

public interface SystemFactory {public OperationController createOperationController();public UIController createInterfaceController();
}

在各平臺具體的工廠類中完成操作控制器和界面控制器的創建過程。

Android:

public class AndroidFactory implements SystemFactory {@Overridepublic OperationController createOperationController() {return new AndroidOperationController();}@Overridepublic UIController createInterfaceController() {return new AndroidUIController();}
}

IOS:

public class IosFactory implements SystemFactory {@Overridepublic OperationController createOperationController() {return new IosOperationController();}@Overridepublic UIController createInterfaceController() {return new IosUIController();}
}

WP:

public class WpFactory implements SystemFactory {@Overridepublic OperationController createOperationController() {return new WpOperationController();}@Overridepublic UIController createInterfaceController() {return new WpUIController();}
}

下面總結一下抽象工廠的適用場景:

  • 和工廠方法一樣客戶端不需要知道它所創建的對象的類。
  • 需要一組對象共同完成某種功能時。并且可能存在多組對象完成不同功能的情況。
  • 系統結構穩定,不會頻繁的增加對象。

1.6 簡單工廠模式和抽象工廠模式有什么區別?

參考答案

簡單工廠模式其實并不算是一種設計模式,更多的時候是一種編程習慣。簡單工廠的實現思路是,定義一個工廠類,根據傳入的參數不同返回不同的實例,被創建的實例具有共同的父類或接口。

工廠方法模式是簡單工廠的僅一步深化, 在工廠方法模式中,我們不再提供一個統一的工廠類來創建所有的對象,而是針對不同的對象提供不同的工廠。也就是說每個對象都有一個與之對應的工廠。工廠方法的實現思路是,定義一個用于創建對象的接口,讓子類決定將哪一個類實例化。工廠方法模式讓一個類的實例化延遲到其子類。

抽象工廠模式是工廠方法的僅一步深化,在這個模式中的工廠類不單單可以創建一個對象,而是可以創建一組對象。這是和工廠方法最大的不同點。抽象工廠的實現思路是,提供一個創建一系列相關或相互依賴對象的接口,而無須指定它們具體的類。

1.7 如何實現工廠模式?

參考答案

簡單工廠

示例:

創建一個可以繪制不同形狀的繪圖工具,可以繪制圓形,正方形,三角形,每個圖形都會有一個draw()方法用于繪圖,不看代碼先考慮一下如何通過該模式設計完成此功能。

由題可知圓形,正方形,三角形都屬于一種圖形,并且都具有draw方法,所以首先可以定義一個接口或者抽象類,作為這三個圖像的公共父類,并在其中聲明一個公共的draw方法:

public interface Shape {void draw();
}

下面就是編寫具體的圖形,每種圖形都實現Shape接口:

// 圓形
class CircleShape implements Shape {public CircleShape() {System.out.println("CircleShape: created");}@Overridepublic void draw() {System.out.println("draw: CircleShape");}
}
// 正方形
class RectShape implements Shape {public RectShape() {System.out.println("RectShape: created");}@Overridepublic void draw() {System.out.println("draw: RectShape");}}
// 三角形
public class TriangleShape implements Shape {public TriangleShape() {System.out.println("TriangleShape: created");}@Overridepublic void draw() {System.out.println("draw: TriangleShape");}
}

下面是工廠類的具體實現:

 class ShapeFactory {public static Shape getShape(String type) {Shape shape = null;if (type.equalsIgnoreCase("circle")) {shape = new CircleShape();} else if (type.equalsIgnoreCase("rect")) {shape = new RectShape();} else if (type.equalsIgnoreCase("triangle")) {shape = new TriangleShape();}return shape;}}

為工廠類傳入不同的type可以new不同的形狀,返回結果為Shape 類型,這個就是簡單工廠核心的地方了。

工廠方法

示例:

現在需要設計一個這樣的圖片加載類,它具有多個圖片加載器,用來加載jpg,png,gif格式的圖片,每個加載器都有一個read()方法,用于讀取圖片。下面我們完成這個圖片加載類。

首先完成圖片加載器的設計,編寫一個加載器的公共接口:

public interface Reader {void read();
}

然后完成各個圖片加載器的代碼:

// jpg圖片加載器
class JpgReader implements Reader {@Overridepublic void read() {System.out.print("read jpg");}
}
// png圖片加載器
class PngReader implements Reader {@Overridepublic void read() {System.out.print("read png");}
}
// gif圖片加載器
class GifReader implements Reader {@Overridepublic void read() {System.out.print("read gif");}
}

現在我們按照定義所說定義一個抽象的工廠接口ReaderFactory:

interface ReaderFactory {Reader getReader();
}

里面有一個getReader()方法返回我們的Reader 類,接下來我們把上面定義好的每個圖片加載器都提供一個工廠類,這些工廠類實現了ReaderFactory 。

// jpg加載器工廠
class JpgReaderFactory implements ReaderFactory {@Overridepublic Reader getReader() {return new JpgReader();}
}
// png加載器工廠
class PngReaderFactory implements ReaderFactory {@Overridepublic Reader getReader() {return new PngReader();}
}
// gif加載器工廠
class GifReaderFactory implements ReaderFactory {@Overridepublic Reader getReader() {return new GifReader();}
}

在每個工廠類中我們都通過重寫的getReader()方法返回各自的圖片加載器對象。

抽象工廠

示例:

現在需要做一款跨平臺的游戲,需要兼容Android,Ios,Wp三個移動操作系統,該游戲針對每個系統都設計了一套操作控制器(OperationController)和界面控制器(UIController),下面通過抽閑工廠方式完成這款游戲的架構設計。

由題可知,游戲里邊的各個平臺的UIController和OperationController應該是我們最終生產的具體產品。所以新建兩個抽象產品接口。

抽象操作控制器:

interface OperationController {void control();
}

抽象界面控制器:

interface UIController {void display();
}

然后完成各個系統平臺的具體操作控制器和界面控制器。

Android:

class AndroidOperationController implements OperationController {@Overridepublic void control() {System.out.println("AndroidOperationController");}
}
class AndroidUIController implements UIController {@Overridepublic void display() {System.out.println("AndroidInterfaceController");}
}

IOS:

class IosOperationController implements OperationController {@Overridepublic void control() {System.out.println("IosOperationController");}
}
class IosUIController implements UIController {@Overridepublic void display() {System.out.println("IosInterfaceController");}
}

WP:

class WpOperationController implements OperationController {@Overridepublic void control() {System.out.println("WpOperationController");}
}
class WpUIController implements UIController {@Overridepublic void display() {System.out.println("WpInterfaceController");}
}

下面定義一個抽閑工廠,該工廠需要可以創建OperationController和UIController。

public interface SystemFactory {public OperationController createOperationController();public UIController createInterfaceController();
}

在各平臺具體的工廠類中完成操作控制器和界面控制器的創建過程。

Android:

public class AndroidFactory implements SystemFactory {@Overridepublic OperationController createOperationController() {return new AndroidOperationController();}@Overridepublic UIController createInterfaceController() {return new AndroidUIController();}
}

IOS:

public class IosFactory implements SystemFactory {@Overridepublic OperationController createOperationController() {return new IosOperationController();}@Overridepublic UIController createInterfaceController() {return new IosUIController();}
}

WP:

public class WpFactory implements SystemFactory {@Overridepublic OperationController createOperationController() {return new WpOperationController();}@Overridepublic UIController createInterfaceController() {return new WpUIController();}
}

1.8 說一說你策略模式的理解

參考答案

策略模式(Strategy Pattern)也叫政策模式,是一種比較簡單的模式。它的目的是定義一組算法,將每個算法都封裝起來,并且使它們之間可以互換。其用意是針對一組算法,將每一個算法封裝到具有共同接口的獨立的類中,從而使得它們可以相互替換,使得算法可以在不影響到客戶端的情況下發生變化。

策略模式的通用類圖如下圖所示:

img

策略模式涉及以下3個角色:

  • 環境(Context)角色:該角色也叫上下文角色,起到承上啟下的作用,屏蔽高層模塊對策略、算法的直接訪問,它持有一個Strategy類的引用。
  • 抽象策略(Strategy)角色:該角色對策略、算法進行抽象,通常定義每個策略或算法必須具有的方法和屬性。
  • 具體策略(Concrete Strategy)角色:該角色實現抽象策略中的具體操作,含有具體的算法。

抽象策略Strategy的代碼如下所示:

public abstract class Strategy {public abstract void strategyInterface();
}

具體策略ConcreteStrategy的代碼如下所示:

public class ConcreteStrategy extends Strategy {public void strategyInterface() {...}
}

環境角色Context的代碼如下所示:

public class Context {private Strategy strategy = null;public Context(Strategy strategy) {this.strategy = strategy;}public void contextInterface() {this.strategy.strategyInterface();}
}

策略模式包括如下優點:

  • 策略模式提供了管理相關的算法族的辦法。策略類的等級結構定義了一個算法或行為族,恰當地使用繼承可以把公共的代碼移到父類中,從而避免代碼重復。
  • 策略模式提供了可以替換繼承關系的辦法。繼承可以處理多種算法或行為,如果不用策略模式,那么使用算法或行為的環境類就可能會有一些子類,每一個子類提供一個不同的算法或行為。但是,這樣算法或行為的使用者就和算法本身混在一起,從而不可能再獨立演化。
  • 使用策略模式可以避免使用多重條件轉移語句。多重轉移語句不易維護,它把采取哪一種算法或采取哪一種行為的邏輯與算法或行為的邏輯混合在一起,統統列在一個多重轉移語句里面,這比使用繼承的辦法還要原始和落后。

策略模式包括如下缺點:

  • 客戶端必須知道所有的策略類,并自行決定使用哪一個策略類。這就意味著客戶端必須理解這些算法的區別,以便適時選擇恰當的算法類,即策略模式只適用于客戶端知道所有的算法或行為的情況。
  • 策略模式造成很多的策略類。有時候可以通過把依賴于環境的狀態保持到客戶端里面,而將策略類設計成可共享的,這樣策略類實例可以被不同客戶端使用。可以使用享元模式來減少對象的數量。

策略模式有如下幾個應用場景:

  • 多個類只是在算法或行為上稍有不同的場景。
  • 算法需要自由切換的場景。
  • 需要屏蔽算法規則的場景。

1.9 說一說你對觀察者模式的了解

參考答案

觀察者模式(Observer Pattern)也稱發布訂閱模式,它的目的是定義對象間一種一對多的依賴關系,使得每當一個對象改變狀態,則所有依賴于它的對象都會得到通知并被自動更新。

觀察者模式的類圖如下圖所:

img

觀察者模式具有以下4個角色:

  • 抽象主題(Subject)角色:該角色又稱為“被觀察者”,可以增加和刪除觀察者對象。
  • 抽象觀察者(Observer)角色:該角色為所有的具體觀察者定義一個接口,在得到主題的通知時更新自己。
  • 具體主題(Concrete Subject)角色:該角色又稱為“具體被觀察者”,它將有關狀態存入具體觀察者對象,在具體主題的內部狀態改變時,給所有登記過的觀察者發出通知。
  • 具體觀察者(Concrete Observer)角色:該角色實現抽象觀察者所要求的更新接口,以便使自身的狀態與主題的狀態相協調。

上述類圖所涉及的代碼如下所示:

interface Subject {// 登記一個新的觀察者public void attach(Observer obs);// 刪除一個登記過的觀察者public void detach(Observer obs);// 通知所有登記過的觀察者對象public void notifyObserver();
}
interface Observer {// 更新方法public void update();
}
class ConcreteSubject implements Subject {private Vector<Observer> obsVector = new Vector<Observer>();// 登記一個新的觀察者public void attach(Observer obs) {obsVector.add(obs);}// 刪除一個登記過的觀察者public void detach(Observer obs) {obsVector.remove(obs);}// 通知所有登記過的觀察者對象public void notifyObserver() {for (Observer e : obsVector) {e.update();}}// 返回觀察者集合的Enumeration對象public Enumeration<Observer> observers() {return obsVector.elements();}// 業務方法,改變狀態public void change() {this.notifyObserver();}
}
class ConcreteObserver implements Observer {// 實現更新方法public void update() {System.out.println("收到通知,并進行處理!");}
}

觀察者模式具有以下幾個優點:

  • 觀察者和被觀察者之間是抽象耦合。被觀察者角色所知道的只是一個具體觀察者集合,每一個具體觀察者都符合一個抽象觀察者的接口。被觀察者并不認識任何一個具體的觀察者,它只知道它們都有一個共同的接口。由于被觀察者和觀察者沒有緊密的耦合在一起,因此它們可以屬于不同的抽象化層次,且都非常容易擴展。
  • 支持廣播通信。被觀察者會向所有登記過的觀察者發出通知,這就是一個觸發機制,形成一個觸發鏈。

觀察模式的缺點如下:

  • 如果一個主題有多個直接或間接的觀察者,則通知所有的觀察者會花費很多時間,且開發和調試都比較復雜。
  • 如果在主題之間有循環依賴,被觀察者會觸發它們之間進行循環調用,導致系統崩潰。在使用觀察者模式時要特別注意這一點。
  • 如果對觀察者的通知是通過另外的線程進行異步投遞,系統必須保證投遞的順序執行。
  • 雖然觀察者模式可以隨時使觀察者知道所觀察的對象發生了變化,但是觀察者模式沒有提供相應的機制使觀察者知道所觀察的對象是如何發生變化。

觀察者模式的應用場景如下:

  • 關聯行為場景。
  • 事件多級觸發場景。
  • 跨系統的消息交換場景,如消息隊列的處理機制。

1.10 說一說你對責任鏈模式的了解

參考答案

責任鏈模式(Chain of Responsibility Pattern)是一種常見的行為模式,它的目的是使多個對象都有機會處理請求,從而避免了請求的發送者和接收者之間的耦合關系。將這些對象連成一條鏈,并沿著這條鏈傳遞該請求,直到有對象處理它為止。

責任鏈模式的重點是在“鏈”上,由一條鏈去處理相似的請求,在鏈中決定誰來處理這個請求,并返回相應的結果。責任鏈模式的類圖如下圖所示:

img

責任鏈模式涉及以下兩個角色:

  • 抽象處理者(Handler)角色:該角色對請求進行抽象,并定義一個方法以設定和返回對下一個處理者的引用。
  • 具體處理者(Concrete Handler)角色:該角色接到請求后,可以選擇將請求處理掉,或者將請求傳給下一個處理者。由于具體處理者持有對下一個處理者的引用,因此,如果需要,具體處理者可以訪問下一個處理者。

上述類圖所涉及的代碼如下所示:

abstract class Handler {private Handler successor;public abstract void handleRequest();public Handler getSuccessor() {return successor;}public void setSuccessor(Handler successor) {this.successor = successor;}
}
class ConcreteHandler extends Handler {// 處理請求public void handleRequest() {if (getSuccessor() != null) {System.out.println("請求傳遞給" + getSuccessor());getSuccessor().handleRequest();} else {System.out.println("請求處理");}}
}

責任鏈模式的優點如下:

  • 責任鏈模式將請求和處理分開,請求者不知道是誰處理的,處理者可以不用知道請求的全貌。
  • 提高系統的靈活性。

責任鏈模式的缺點如下:

  • 降低程序的性能,每個請求都是從鏈頭遍歷到鏈尾,當鏈比較長的時候,性能會大幅下降。
  • 不易于調試,由于采用了類似遞歸的方式,調試的時候邏輯比較復雜。

責任鏈模式的應用場景如下:

  • 一個請求需要一系列的處理工作。
  • 業務流的處理,例如,文件審批。
  • 對系統進行補充擴展。

1.11 說一說裝飾器模式和適配器模式的區別

參考答案

裝飾器的目的是動態地給一個對象添加一些額外的職責,這個對象的類型不會發生變化,但是行為卻發生了改變。

適配器的目的是將一個類的接口變換成客戶端所期待的另一種接口,就是可以將一個對象包裝成另外的一個接口。

1.12 Spring框架中用到了哪些設計模式?

Spring框架在實現時運用了大量的設計模式,常見的有如下幾種:

  1. 簡單工廠

    Spring中的BeanFactory就是簡單工廠模式的體現,根據傳入一個唯一的標識來獲得Bean對象,但是否是在傳入參數后創建還是傳入參數前創建這個要根據具體情況來定。

  2. 工廠方法

    實現了FactoryBean接口的bean是一類叫做factory的bean。其特點是,spring會在使用getBean()調用獲得該bean時,會自動調用該bean的getObject()方法,所以返回的不是factory這個bean,而是這個bean的getOjbect()方法的返回值。

  3. 單例模式

    Spring依賴注入Bean實例默認是單例的。Spring的依賴注入(包括lazy-init方式)都是發生在AbstractBeanFactory的getBean里。getBean的doGetBean方法調用getSingleton進行bean的創建。

  4. 適配器模式

    SpringMVC中的適配器HandlerAdatper,它會根據Handler規則執行不同的Handler。即DispatcherServlet根據HandlerMapping返回的handler,向HandlerAdatper發起請求處理Handler。HandlerAdapter根據規則找到對應的Handler并讓其執行,執行完畢后Handler會向HandlerAdapter返回一個ModelAndView,最后由HandlerAdapter向DispatchServelet返回一個ModelAndView。

  5. 裝飾器模式

    Spring中用到的裝飾器模式在類名上有兩種表現:一種是類名中含有Wrapper,另一種是類名中含有Decorator。

  6. 代理模式

    AOP底層就是動態代理模式的實現。即:切面在應用運行的時刻被織入。一般情況下,在織入切面時,AOP容器會為目標對象創建動態的創建一個代理對象。SpringAOP就是以這種方式織入切面的。

  7. 觀察者模式

    Spring的事件驅動模型使用的是觀察者模式,Spring中Observer模式常用的地方是listener的實現。

  8. 策略模式

    Spring框架的資源訪問Resource接口。該接口提供了更強的資源訪問能力,Spring 框架本身大量使用了 Resource 接口來訪問底層資源。Resource 接口是具體資源訪問策略的抽象,也是所有資源訪問類所實現的接口。

  9. 模板方法模式

個要根據具體情況來定。

  1. 工廠方法

    實現了FactoryBean接口的bean是一類叫做factory的bean。其特點是,spring會在使用getBean()調用獲得該bean時,會自動調用該bean的getObject()方法,所以返回的不是factory這個bean,而是這個bean的getOjbect()方法的返回值。

  2. 單例模式

    Spring依賴注入Bean實例默認是單例的。Spring的依賴注入(包括lazy-init方式)都是發生在AbstractBeanFactory的getBean里。getBean的doGetBean方法調用getSingleton進行bean的創建。

  3. 適配器模式

    SpringMVC中的適配器HandlerAdatper,它會根據Handler規則執行不同的Handler。即DispatcherServlet根據HandlerMapping返回的handler,向HandlerAdatper發起請求處理Handler。HandlerAdapter根據規則找到對應的Handler并讓其執行,執行完畢后Handler會向HandlerAdapter返回一個ModelAndView,最后由HandlerAdapter向DispatchServelet返回一個ModelAndView。

  4. 裝飾器模式

    Spring中用到的裝飾器模式在類名上有兩種表現:一種是類名中含有Wrapper,另一種是類名中含有Decorator。

  5. 代理模式

    AOP底層就是動態代理模式的實現。即:切面在應用運行的時刻被織入。一般情況下,在織入切面時,AOP容器會為目標對象創建動態的創建一個代理對象。SpringAOP就是以這種方式織入切面的。

  6. 觀察者模式

    Spring的事件驅動模型使用的是觀察者模式,Spring中Observer模式常用的地方是listener的實現。

  7. 策略模式

    Spring框架的資源訪問Resource接口。該接口提供了更強的資源訪問能力,Spring 框架本身大量使用了 Resource 接口來訪問底層資源。Resource 接口是具體資源訪問策略的抽象,也是所有資源訪問類所實現的接口。

  8. 模板方法模式

    Spring模板方法模式的實質,是模板方法模式和回調模式的結合,是Template Method不需要繼承的另一種實現方式。Spring幾乎所有的外接擴展都采用這種模式。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/450697.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/450697.shtml
英文地址,請注明出處:http://en.pswp.cn/news/450697.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Android內存優化之內存泄漏

內存泄漏 內存泄漏一般有以下幾種情況&#xff1a;單例、靜態變量、Handler、匿名內部類、資源使用未關閉 單例導致的內存泄漏 單例的情況主要是因為單例的生命周期比較長&#xff0c;如果引用的一些資源&#xff08;比如Context、圖片等&#xff09;沒有做特殊處理&#xff0c…

cmd - 使用curl命令的注意點

前言 最近在cmd中使用curl命令來測試rest api&#xff0c;發現有不少問題&#xff0c;這里記錄一下。 在cmd中使用curl命令的注意事項 json不能由單引號包括起來json數據里的雙引號要用反斜杠\轉義json數據里不能帶有空格如果想要在json數據里使用空格則必須用雙引號將整個json…

指針常見定義

再給出常用的C變量的定義方式&#xff1a;a) 一個整型數&#xff08;An integer&#xff09; b) 一個指向整型數的指針&#xff08;A pointer to an integer&#xff09; c) 一個指向指針的的指針&#xff0c;它指向的指針是指向一個整型數&#xff08;A pointer to a pointer …

場景應用題目常見面試真題詳解

文章目錄1. 場景應用1.1 微信紅包相關問題1.2 秒殺系統相關問題1.3 掃碼登錄流程1.4 如何實現單點登錄&#xff1f;1.5 如何設計一個本地緩存&#xff1f;1. 場景應用 1.1 微信紅包相關問題 參考答案 概況&#xff1a;2014年微信紅包使用數據庫硬抗整個流量&#xff0c;2015…

后Kubernetes時代的微服務

\本文要點\\當前微服務架構依然是最流行的分布式系統架構風格。Kubernetes和云原生運動已大規模地重新定義了應用設計和開發中的一些方面。\\t在云原生平臺上&#xff0c;服務僅具備可觀測性是不夠的。更基本的先決條件是使用檢查健康、響應信號、聲明資源消耗等手段實現微服務…

Dynamics CRM On-Premise V9安裝手記

下載地址&#xff1a; https://download.microsoft.com/download/A/D/D/ADDD6898-4EFA-46FA-80B6-6FE9A3CDED63/CRM9.0-Server-CHS-amd64.exe 安裝支持Windows 2016 及SQL Server 2016 SP2以上版本 我想安裝了All in one的&#xff0c;就想著用最新的SQLServer 2017&#xff0c…

金山網絡CEO傅盛:簡約之美

摘要&#xff1a;金山網絡CEO傅盛帶來了主題為《簡約之美》的精彩演講。他表示由于時代的變遷&#xff0c;紅海的競爭&#xff0c;項目的需求等原因&#xff0c;若想項目取得成功&#xff0c;唯有簡單才是王道&#xff0c;唯有簡單定位才能深入人心。那么&#xff0c;如何做到簡…

zookeeper安裝和使用 windows環境

簡介 前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程。 ZooKeeper是一個分布式的&#xff0c;開放源碼的分布式應用程序協調服務&#xff0c;是Google的Chubby一個開源的實現&#xff0c;…

計算機網絡常見面試真題詳解

文章目錄1. 計算機網絡1.1 請介紹七層網絡體系結構。1.2 請介紹五層網絡體系結構。1.3 了解網絡編程協議嗎&#xff1f;客戶端發送給服務器的請求&#xff0c;怎么確定具體的協議&#xff1f;1.4 TCP、HTTP、FTP分別屬于哪一層&#xff1f;1.5 講一下TCP/IP協議。1.6 說一說你對…

2018.09.14python學習第四天part2

流程控制之while循環 1.什么是循環&#xff1f;&#xff08;what&#xff09; 循環是指重復做某一件事 2.為何要有循環&#xff1f;&#xff08;why&#xff09; 為了讓計算機能像人一樣重復去做某一件事 3.如何使用循環&#xff1f;&#xff08;how&#xff09; #語法一&#…

git操作指令合集

1.下載完git&#xff0c;需要輸入用戶名和郵箱 git config --global user.name "Your Name" git config --global user.email "emailexample.com" 注意git config命令的--global參數&#xff0c;用了這個參數&#xff0c;表示這臺電腦上所有的GIt倉庫都會使…

C++回調函數(callback)的使用

什么是回調函數(callback) 模塊A有一個函數foo&#xff0c;它向模塊B傳遞foo的地址&#xff0c;然后在B里面發生某種事件&#xff08;event&#xff09;時&#xff0c;通過從A里面傳遞過來的foo的地址調用foo&#xff0c;通知A發生了什么事情&#xff0c;讓A作出相應反應。 那么…

Hibernate JPA中@Transient、@JsonIgnoreProperties、@JsonIgnore、@JsonFormat、@JsonSerialize等注解解釋

前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程。 1、Transient Transient表示該屬性并非一個到數據庫表的字段的映射,ORM框架將忽略該屬性&#xff1b; 如果一個屬性并非數據庫表的字段…

可愛的rem

前端開發中&#xff0c;移動端的開發可以說是舉足輕重了&#xff0c;可是又面臨著不同設備尺寸和分辨率的尷尬點。今天[2018-09-16]臺風山竹登陸廣東&#xff0c;來勢洶洶&#xff0c;外出是不可能的了&#xff0c;那就宅著寫寫這篇小文章吧...原文請戳這里-談談rem單位 超長的…

kafka直連方式消費多個topic

一個消費者組可以消費多個topic&#xff0c;以前寫過一篇一個消費者消費一個topic的&#xff0c;這次的是一個消費者組通過直連方式消費多個topic,做了小測試&#xff0c;結果是正確的&#xff0c;通過查看zookeeper的客戶端&#xff0c;zookeeper記錄了偏移量 package day04 /…

100個經典的C語言算法

100個經典的C算法 C語言的學習要從基礎開始&#xff0c;這里是100個經典的算法 題目&#xff1a;古典問題&#xff1a;有一對兔子&#xff0c;從出生后第3個月起每個月都生一對兔子&#xff0c;小兔 子長到第三個月后每個月又生一對兔子&#xff0c;假如兔子都不死&#xff0c;…

MySQL常見面試題目詳解

文章目錄1. SQL1.1 介紹一下數據庫分頁1.2 介紹一下SQL中的聚合函數1.3 表跟表是怎么關聯的&#xff1f;1.4 說一說你對外連接的了解1.5 說一說數據庫的左連接和右連接1.6 SQL中怎么將行轉成列&#xff1f;1.7 談談你對SQL注入的理解1.8 將一張表的部分數據更新到另一張表&…

[轉]windows系統激活

原文鏈接主題&#xff1a;使用kms激活&#xff0c;可以直接使用命令來完成。 方法&#xff1a;在win10桌面狀態下&#xff0c;右擊windows徽標或按快捷鍵windowsx&#xff0c;點擊命令提示符&#xff08;管理員&#xff09; 用到的命令是slmgr&#xff0c;手動kms激活命令如下&…

jackson annotations注解詳解

前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程。 官方WIKI&#xff1a;https://github.com/FasterXML/jackson-databind/wiki jackson 1.x和2.x版本的注解是放置在不同的包下的 1.x是在…

JS-for的衍生對象

在js中一般使用方法&#xff1a; 1.常規的for(var i0;i<length;i) 2.for-in:for(var item in list) 3.for of 描述&#xff1a;對應于一個對象的每個屬性&#xff0c;或一個數組的每個元素&#xff0c;執行一個或多個語句。 語法&#xff1a;for (variable in [object | ar…