設計模式(三)(單例模式、命令模式)
六:單例模式
單例模式確保一個類只有一個例項,並提供一個全域性訪問點。
意圖:保證一個類僅有一個例項,並提供一個訪問它的全域性訪問點。
主要解決:一個全域性使用的類頻繁地建立與銷燬。
何時使用:當您想控制例項數目,節省系統資源的時候。
如何解決:判斷系統是否已經有這個單例,如果有則返回,如果沒有則建立。
關鍵程式碼:建構函式是私有的。
應用例項: 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、命令也可以用來實現日誌和事務系統。