設計模式簡介和單例模式

設計模式分類

創建型模式,共五種:工廠方法模式、抽象工廠模式單例模式、建造者模式、原型模式。

結構型模式,共七種:適配器模式、裝飾器模式、代理模式、外觀模式、橋接模式、組合模式、享元模式。

行爲型模式,共十一種:策略模式、模板方法模式、觀察者模式、迭代子模式、責任鏈模式、命令模式、備忘錄模式、狀態模式、訪問者模式、中介者模式、解釋器模式。

 

設計模式的六大原則

開閉原則(Open Close Principle)

開閉原則就是說對擴展開放,對修改關閉。在程序需要進行拓展的時候,不能去修改原有的代碼,實現一個熱插拔的效果。所以一句話概括就是:爲了使程序的擴展性好,易於維護和升級。想要達到這樣的效果,我們需要使用接口和抽象類,後面的具體設計中我們會提到這點。

 

里氏代換原則(Liskov Substitution Principle)

里氏代換原則(Liskov Substitution Principle LSP)面向對象設計的基本原則之一。 里氏代換原則中說,任何基類可以出現的地方,子類一定可以出現。 LSP是繼承複用的基石,只有當衍生類可以替換掉基類,軟件單位的功能不受到影響時,基類才能真正被複用,而衍生類也能夠在基類的基礎上增加新的行爲。里氏代換原則是對“開-閉”原則的補充。實現“開-閉”原則的關鍵步驟就是抽象化。而基類與子類的繼承關係就是抽象化的具體實現,所以里氏代換原則是對實現抽象化的具體步驟的規範。—— From Baidu 百科

 

依賴倒轉原則(Dependence Inversion Principle)

這個是開閉原則的基礎,具體內容:真對接口編程,依賴於抽象而不依賴於具體。

 

接口隔離原則(Interface Segregation Principle)

這個原則的意思是:使用多個隔離的接口,比使用單個接口要好。還是一個降低類之間的耦合度的意思,從這兒我們看出,其實設計模式就是一個軟件的設計思想,從大型軟件架構出發,爲了升級和維護方便。所以上文中多次出現:降低依賴,降低耦合。

 

迪米特法則(最少知道原則)(Demeter Principle)

爲什麼叫最少知道原則,就是說:一個實體應當儘量少的與其他實體之間發生相互作用,使得系統功能模塊相對獨立。

 

合成複用原則(Composite Reuse Principle)

原則是儘量使用合成/聚合的方式,而不是使用繼承。

 

單例模式

什麼

 保證一個類只有一個實例,並且提供一個訪問該全局訪問點

應用場景

1. Windows的Task Manager(任務管理器)就是很典型的單例模式(這個很熟悉吧),想想看,是不是呢,你能打開兩個windows task manager嗎? 不信你自己試試看哦~

2. windows的Recycle Bin(回收站)也是典型的單例應用。在整個系統運行過程中,回收站一直維護着僅有的一個實例。

3. 網站的計數器,一般也是採用單例模式實現,否則難以同步。

4. 應用程序的日誌應用,一般都何用單例模式實現,這一般是由於共享的日誌文件一直處於打開狀態,因爲只能有一個實例去操作,否則內容不好追加。

5. Web應用的配置對象的讀取,一般也應用單例模式,這個是由於配置文件是共享的資源。

6. 數據庫連接池的設計一般也是採用單例模式,因爲數據庫連接是一種數據庫資源。數據庫軟件系統中使用數據庫連接池,主要是節省打開或者關閉數據庫連接所引起的效率損耗,這種效率上的損耗還是非常昂貴的,因爲何用單例模式來維護,就可以大大降低這種損耗。

7. 多線程的線程池的設計一般也是採用單例模式,這是由於線程池要方便對池中的線程進行控制。

8. 操作系統的文件系統,也是大的單例模式實現的具體例子,一個操作系統只能有一個文件系統。

9. HttpApplication 也是單位例的典型應用。熟悉ASP.Net(IIS)的整個請求生命週期的人應該知道HttpApplication也是單例模式,所有的HttpModule都共享一個HttpApplication實例.

 

優缺點

優點:

    1.在單例模式中,活動的單例只有一個實例,對單例類的所有實例化得到的都是相同的一個實例。這樣就 防止其它對象對自己的實例化,確保所有的對象都訪問一個實例

    2.單例模式具有一定的伸縮性,類自己來控制實例化進程,類就在改變實例化進程上有相應的伸縮性。

    3.提供了對唯一實例的受控訪問。

    4.由於在系統內存中只存在一個對象,因此可以 節約系統資源,當 需要頻繁創建和銷燬的對象時單例模式無疑可以提高系統的性能。

    5.允許可變數目的實例。

    6.避免對共享資源的多重佔用。

缺點:

    1.不適用於變化的對象,如果同一類型的對象總是要在不同的用例場景發生變化,單例就會引起數據的錯誤,不能保存彼此的狀態。

    2.由於單利模式中沒有抽象層,因此單例類的擴展有很大的困難。

    3.單例類的職責過重,在一定程度上違背了“單一職責原則”。

    4.濫用單例將帶來一些負面問題,如爲了節省資源將數據庫連接池對象設計爲的單例類,可能會導致共享連接池對象的程序過多而出現連接池溢出;如果實例化的對象長時間不被利用,系統會認爲是垃圾而被回收,這將導致對象狀態的丟失。

創建方式

  1. 餓漢:類初始化時,會立即加載該對象,線程天生安全,調用效率高。
  2. 懶漢: 類初始化時,不會初始化該對象,真正需要使用的時候纔會創建該對象,具備懶加載功能。
  3. 靜態內部方式:結合了懶漢式和餓漢式各自的優點,真正需要對象的時候纔會加載,加載類是線程安全的。
  4. 枚舉單例: 使用枚舉實現單例模式 優點:實現簡單、調用效率高,枚舉本身就是單例,由jvm從根本上提供保障!避免通過反射和反序列化的漏洞, 缺點沒有延遲加載。
  5. 雙重檢測鎖方式 (因爲JVM本質重排序的原因,可能會初始化多次,不推薦使用)

 

餓漢式

//餓漢式,即上來就搞一個實例放到方法區(永久區,這區不會被GC回收)
public class SingletonDemo01 {
	// 類初始化時,會立即加載該對象,線程天生安全,調用效率高
	private static SingletonDemo01 singletonDemo01 = new SingletonDemo01();

	private SingletonDemo01() {
		System.out.println("SingletonDemo01初始化");
	}

	public static SingletonDemo01 getInstance() {
		System.out.println("getInstance");
		return singletonDemo01;
	}

	public static void main(String[] args) {
		SingletonDemo01 s1 = SingletonDemo01.getInstance();
		SingletonDemo01 s2 = SingletonDemo01.getInstance();
		System.out.println(s1 == s2);
	}

}

懶漢式

//懶漢式
public class SingletonDemo02 {

	//類初始化時,不會初始化該對象,真正需要使用的時候纔會創建該對象。
	private static SingletonDemo02 singletonDemo02;

	private SingletonDemo02() {
   
	}

	public synchronized static SingletonDemo02 getInstance() {
		if (singletonDemo02 == null) {
			singletonDemo02 = new SingletonDemo02();
		}
		return singletonDemo02;
	}

	public static void main(String[] args) {
		SingletonDemo02 s1 = SingletonDemo02.getInstance();
		SingletonDemo02 s2 = SingletonDemo02.getInstance();
		System.out.println(s1 == s2);
	}

}

靜態內部類

// 靜態內部類方式
public class SingletonDemo03 {
	private SingletonDemo03() {
           System.out.println("初始化..");
	}

	public static class SingletonClassInstance {
		private static final SingletonDemo03 singletonDemo03 = new SingletonDemo03();
	}

	// 方法沒有同步
	public static SingletonDemo03 getInstance() {
		System.out.println("getInstance");
		return SingletonClassInstance.singletonDemo03;
	}

	public static void main(String[] args) {
		SingletonDemo03 s1 = SingletonDemo03.getInstance();
		SingletonDemo03 s2 = SingletonDemo03.getInstance();
		System.out.println(s1 == s2);
	}
}

優勢:兼顧了懶漢模式的內存優化(使用時才初始化)以及餓漢模式的安全性(不會被反射入侵)。

劣勢:需要兩個類去做到這一點,雖然不會創建靜態內部類的對象,但是其 Class 對象還是會被創建,而且是屬於永久代的對象。

枚舉方式

什麼是枚舉

枚舉本身是單例的,一般用於項目中定義常量。

enum UserEnum {
	HTTP_200(200, "請求成功"),HTTP_500(500,"請求失敗");
	private Integer code;
	private String name;

	UserEnum(Integer code, String name) {
		this.code = code;
		this.name = name;
	}

	public Integer getCode() {
		return code;
	}

	public void setCode(Integer code) {
		this.code = code;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

}

public class TestEnum {

	public static void main(String[] args) {
		System.out.println(UserEnum.HTTP_500.getCode());
	}

}

通過枚舉實現單例對象創建

/使用枚舉實現單例模式 優點:實現簡單、枚舉本身就是單例,由jvm從根本上提供保障!避免通過反射和反序列化的漏洞 缺點沒有延遲加載
public class User {
	public static User getInstance() {
		return SingletonDemo04.INSTANCE.getInstance();
	}

	private static enum SingletonDemo04 {
		INSTANCE;
		// 枚舉元素爲單例
		private User user;

		private SingletonDemo04() {
			System.out.println("SingletonDemo04");
			user = new User();
		}

		public User getInstance() {
			return user;
		}
	}

	public static void main(String[] args) {
		User u1 = User.getInstance();
		User u2 = User.getInstance();
		System.out.println(u1 == u2);
	}
}

重檢測鎖

public class SingletonDemo04 {
	private SingletonDemo04 singletonDemo04;

	private SingletonDemo04() {

	}

	public SingletonDemo04 getInstance() {
		if (singletonDemo04 == null) {
			synchronized (this) {
				if (singletonDemo04 == null) {
					singletonDemo04 = new SingletonDemo04();
				}
			}
		}
		return singletonDemo04;
	}

}

雙重鎖檢驗機制,效率低,而且指令重排序下,有問題,一般是加Volatile關鍵字

防止反射漏洞攻擊

在構造函數中,只能允許初始化化一次即可。

private static boolean flag = false;

	private SingletonDemo04() {

		if (flag == false) {
			flag = !flag;
		} else {
			throw new RuntimeException("單例模式被侵犯!");
		}
	}

	public static void main(String[] args) {

	}

如何選擇單例創建方式

如果不需要延遲加載單例,可以使用枚舉或者餓漢式,相對來說枚舉性好於餓漢式。

如果需要延遲加載,可以使用靜態內部類或者懶韓式,相對來說靜態內部類好於懶韓式。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章