1. 程式人生 > >設計模式(三)(單例模式、命令模式)

設計模式(三)(單例模式、命令模式)

六:單例模式

單例模式確保一個類只有一個例項,並提供一個全域性訪問點。

意圖:保證一個類僅有一個例項,並提供一個訪問它的全域性訪問點。

主要解決:一個全域性使用的類頻繁地建立與銷燬。

何時使用:當您想控制例項數目,節省系統資源的時候。

如何解決:判斷系統是否已經有這個單例,如果有則返回,如果沒有則建立。

關鍵程式碼:建構函式是私有的。

應用例項: 1、一個黨只能有一個主席。 2、Windows 是多程序多執行緒的,在操作一個檔案的時候,就不可避免地出現多個程序或執行緒同時操作一個檔案的現象,所以所有檔案的處理必須通過唯一的例項來進行。 3、一些裝置管理器常常設計為單例模式,比如一個電腦有兩臺印表機,在輸出的時候就要處理不能兩臺印表機列印同一個檔案。

優點: 1、在記憶體裡只有一個例項,減少了記憶體的開銷,尤其是頻繁的建立和銷燬例項(比如管理學院首頁頁面快取)。 2、避免對資源的多重佔用(比如寫檔案操作)。

缺點:沒有介面,不能繼承,與單一職責原則衝突,一個類應該只關心內部邏輯,而不關心外面怎麼樣來例項化。

使用場景: 1、要求生產唯一序列號。 2、WEB 中的計數器,不用每次重新整理都在資料庫里加一次,用單例先快取起來。 3、建立的一個物件需要消耗的資源過多,比如 I/O 與資料庫的連線等。

注意事項:getInstance() 方法中需要使用同步鎖 synchronized (Singleton.class) 防止多執行緒同時進入造成 instance 被多次例項化。

實現:

1.懶漢式,執行緒安全

描述:這種方式具備很好的 lazy loading,能夠在多執行緒中很好的工作,但是,效率很低,99% 情況下不需要同步。 優點:第一次呼叫才初始化,避免記憶體浪費。 缺點:必須加鎖 synchronized 才能保證單例,但加鎖會影響效率。 getInstance() 的效能對應用程式不是很關鍵(該方法使用不太頻繁)。

/* 
 * 使用同步塊的方式
* @author zzf 
* @date 2018年10月22日 上午10:02:24 
*/
public class SingletonTwo {
	
	private static SingletonTwo singletonTwo;

	private SingletonTwo() {}

	/**
	 * 通過同步塊的方式進行保證執行緒安全,但是在經常呼叫這個方法的情況下會導致效能降低
	 * @return
	 */
	public static synchronized SingletonTwo getInstance() {
		if(singletonTwo==null) {
			singletonTwo=new SingletonTwo();
		}
			return singletonTwo;
	}
}

2.餓漢式

描述:這種方式比較常用,但容易產生垃圾物件。 優點:沒有加鎖,執行效率會提高。 缺點:類載入時就初始化,浪費記憶體。 它基於 classloder 機制避免了多執行緒的同步問題,不過,instance 在類裝載時就例項化,雖然導致類裝載的原因有很多種,在單例模式中大多數都是呼叫 getInstance 方法, 但是也不能確定有其他的方式(或者其他的靜態方法)導致類裝載,這時候初始化 instance 顯然沒有達到 lazy loading 的效果。

/* 
 * 使用靜態初始化的方式進行初始化
* @author zzf 
* @date 2018年10月22日 上午9:58:56 
*/
public class SingletonOne {

	/**
	 * 在靜態初始化器中建立單例,這段程式碼保證了執行緒安全
	 */
	private static SingletonOne singletonOne =new SingletonOne();
	
	private SingletonOne() {}
	
	public static SingletonOne getInstance() {
		return singletonOne;
	}
}

3.雙檢鎖/雙重校驗鎖(DCL,即 double-checked locking)

描述:這種方式採用雙鎖機制,安全且在多執行緒情況下能保持高效能。 getInstance() 的效能對應用程式很關鍵。

/* 
 * 雙重檢查加鎖(不適用於1.4及以前的版本)
* @author zzf 
* @date 2018年10月22日 上午10:11:02 
*/
public class SingletonThree {

	/**
	 * volatile確保當狀態發生改變時所有執行緒都能發現
	 */
	private volatile static SingletonThree singletonThree;

	private SingletonThree() {}

	/**
	 * 通過同步塊的方式進行保證執行緒安全,但是在經常呼叫這個方法的情況下會導致效能降低
	 * @return
	 */
	public static SingletonThree getInstance() {
		if(singletonThree==null) {//檢查例項,如果不存在就進入同步塊
			synchronized (SingletonThree.class) {//加鎖
				if(singletonThree==null) {//再次判斷是否已經初始化
					singletonThree=new SingletonThree();
				}
			}
		}
			return singletonThree;
	}
}

要點:

1、單例模式確保程式中一個類最多隻有一個例項。

2、單例模式也提供訪問這個例項的全域性點。

3、在Java中實現單例模式需要私有的構造器、一個靜態方法和一個靜態變數。

4、確定在效能和資源上的限制,然後選擇適當的方案來實現單例,以解決多執行緒的問題。

5、如果不是採用第五版Java,雙重檢查加鎖實現會失效。

6、如果使用多個類載入器,可能導致單例失效而產生多個例項。

7、如果使用JVM 1.2或以前的版本,必須建立單例登錄檔,以免垃圾收集器將單例回收。

七:命令模式

命令模式將“請求”封裝成物件,以便使用不同的請求、佇列或者日誌來引數化其他物件。命令模式也支援可撤銷的操作。

意圖:將一個請求封裝成一個物件,從而使您可以用不同的請求對客戶進行引數化。

主要解決:在軟體系統中,行為請求者與行為實現者通常是一種緊耦合的關係,但某些場合,比如需要對行為進行記錄、撤銷或重做、事務等處理時,這種無法抵禦變化的緊耦合的設計就不太合適。

何時使用:在某些場合,比如要對行為進行"記錄、撤銷/重做、事務"等處理,這種無法抵禦變化的緊耦合是不合適的。在這種情況下,如何將"行為請求者"與"行為實現者"解耦?將一組行為抽象為物件,可以實現二者之間的鬆耦合。

如何解決:通過呼叫者呼叫接受者執行命令,順序:呼叫者→接受者→命令。

關鍵程式碼:定義三個角色:1、received 真正的命令執行物件 2、Command 3、invoker 使用命令物件的入口

應用例項:struts 1 中的 action 核心控制器 ActionServlet 只有一個,相當於 Invoker,而模型層的類會隨著不同的應用有不同的模型類,相當於具體的 Command。

優點: 1、降低了系統耦合度。 2、新的命令可以很容易新增到系統中去。

缺點:使用命令模式可能會導致某些系統有過多的具體命令類。

使用場景:認為是命令的地方都可以使用命令模式,比如: 1、GUI 中每一個按鈕都是一條命令。 2、模擬 CMD。

注意事項:系統需要支援命令的撤銷(Undo)操作和恢復(Redo)操作,也可以考慮使用命令模式,見命令模式的擴充套件。

實現:

首先建立一個命令介面:

/* 
 * 命令介面
* @author zzf 
* @date 2018年10月23日 上午10:13:46 
*/
public interface Command {
	//執行
	public void execute();
	//撤銷
	public void undo();
}

現在建立一個電燈廠商類,裡面有兩個方法,off()、on()。

/* 
 * 電燈類
* @author zzf 
* @date 2018年10月23日 上午10:30:45 
*/
public class Light {

	public void off() {
		System.out.println("關燈");
	}
	
	public void on() {
		System.out.println("開燈");
	}
}

下面實現開啟電燈和關燈的命令類,它會繼承command類:

/* 
 * 實現關燈命令
* @author zzf 
* @date 2018年10月23日 上午10:56:24 
*/
public class LightOffCommand implements Command {

	Light light;

	public LightOffCommand(Light light) {
		this.light=light;
	}

	@Override
	public void execute() {
		// TODO Auto-generated method stub
		light.off();
	}

	@Override
	public void undo() {
		// TODO Auto-generated method stub
		light.on();
	}

}
/* 
 * 開啟電燈的命令
* @author zzf 
* @date 2018年10月23日 上午10:29:21 
*/
public class LightOnCommand implements Command {

	Light light;

	public LightOnCommand(Light light) {
		this.light=light;
	}

	@Override
	public void execute() {
		// TODO Auto-generated method stub
		light.on();
	}

	@Override
	public void undo() {
		// TODO Auto-generated method stub
		light.off();
	}

}

使用命令物件:

/* 
 * 控制裝置
* @author zzf 
* @date 2018年10月23日 上午10:34:06 
*/
public class SimpleRemoteControl {

	//開關列表
	List<Command> on=new ArrayList<Command>();
	List<Command> off=new ArrayList<Command>();
	//儲存前一個命令
	Command unCommand;
	
	public SimpleRemoteControl() {}
	
	public void setCommand(int index,Command on,Command off) {
		this.on.add(index,on);
		this.off.add(index,off);
	}
	
	public void OnButtonWasPressed(int index) {
		on.get(index).execute();
		unCommand=on.get(index);
	}
	
	public void OffButtonWasPressed(int index) {
		off.get(index).execute();
		unCommand=off.get(index);
	}
	
	//撤銷命令
	public void undoButtonWasPressed() {
		unCommand.undo();
	}
}

測試:

/* 
* @author zzf 
* @date 2018年10月23日 上午10:36:20 
*/
public class RemoteControlTest {

	public static void main(String[] args) {
		SimpleRemoteControl remote=new SimpleRemoteControl();
		Light light=new Light();
		
		LightOnCommand lightOn=new LightOnCommand(light);
		LightOffCommand lightOff=new LightOffCommand(light);
		
		remote.setCommand(0,lightOn,lightOff);
		remote.OnButtonWasPressed(0);
		remote.undoButtonWasPressed();
		remote.OffButtonWasPressed(0);
		remote.undoButtonWasPressed();
	}
}

要點:

1、命令模式將發出請求的物件和執行請求的物件解耦。

2、在被解耦的兩者之間是通過命令物件進行溝通的。命令物件封裝了接收者和一個或一組動作。

3、呼叫者通過呼叫命令物件的execute()發出請求,這會使得接收者的動作被呼叫。

4、呼叫者可以接受命令當做引數,甚至在執行時動態地進行。

5、命令可以支援撤銷,做法是實現一個undo()方法來回到execute()被執行前的狀態。

6、巨集命令是命令的一種簡單的延伸,允許呼叫多個命令。巨集方法也可以支援銷燬。

7、實際操作時,可以直接實現請求,而不是將工作委託給接受者。

8、命令也可以用來實現日誌和事務系統。