1.1 如何開機
估計有些朋友看到這個标題會非常奇怪,電腦裝配好了,如何開機?不就是按下啟動按鈕就可以了嗎?難道還有什麼玄機不成。
對于使用電腦的客戶——就是我們來說,開機确實很簡單,按下啟動按鈕,然後耐心等待就可以了。但是當我們按下啟動按鈕過後呢?誰來處理?如何處理?都經曆了怎樣的過程,才讓電腦真正的啟動起來,供我們使用。
先一起來簡單的認識一下電腦的啟動過程,了解一下即可。
·當我們按下啟動按鈕,電源開始向主機闆和其它裝置供電
·主機闆的系統BIOS(基本輸入輸出系統)開始加電後自檢
·主機闆的BIOS會依次去尋找顯示卡等其它裝置的BIOS,并讓它們自檢或者初始化
·開始檢測CPU、記憶體、硬碟、光驅、序列槽、并口、軟驅、即插即用裝置等等
·BIOS更新ESCD(擴充系統配置資料),ESCD是BIOS和作業系統交換硬體配置資料的一種手段
·等前面的事情都完成後,BIOS才按照使用者的配置進行系統引導,進入作業系統裡面,等到作業系統裝載并初始化完畢,就出現我們熟悉的系統登入界面了。
1.2 與我何幹
講了一通電腦啟動的過程,有些朋友會想,這與我何幹呢?
沒錯,看起來這些硬體知識跟你沒有什麼大的關系,但是,如果現在提出一個要求:請你用軟體把上面的過程表現出來,你該如何實作?
首先把上面的過程總結一下,主要就這麼幾個步驟:首先加載電源,然後是裝置檢查,再然後是裝載系統,最後電腦就正常啟動了。可是誰來完成這些過程?如何完成?
不能讓使用電腦的客戶——就是我們來做這些工作吧,真正完成這些工作的是主機闆,那麼客戶和主機闆如何發生聯系呢?現實中,是用連接配接線把按鈕連接配接到主機闆上的,這樣當客戶按下按鈕的時候,就相當于發指令給主機闆,讓主機闆去完成後續的工作。
另外,從客戶的角度來看,開機就是按下按鈕,不管什麼樣的主機闆都是一樣的,也就是說,客戶隻管發出指令,誰接收指令,誰實作指令,如何實作,客戶是不關心的。
1.3 有何問題
把上面的問題抽象描述一下:用戶端隻是想要發出指令或者請求,不關心請求的真正接收者是誰,也不關心具體如何實作,而且同一個請求的動作可以有不同的請求内容,當然具體的處理功能也不一樣,請問該怎麼實作?
2 解決方案
2.1 指令模式來解決
用來解決上述問題的一個合理的解決方案就是指令模式。那麼什麼是指令模式呢?
(1)指令模式定義
将一個請求封裝為一個對象,進而使你可用不同的請求對客戶進行參數化;對請求排隊或記錄請求日志,以及支援可撤銷的操作。
(2)應用指令模式來解決的思路
首先來看看實際電腦的解決方案
先畫個圖來描述一下,看看實際的電腦是如何處理上面描述的這個問題的,如圖1所示:
圖1 電腦操作示意圖
當客戶按下按鈕的時候,按鈕本身并不知道如何處理,于是通過連接配接線來請求主機闆,讓主機闆去完成真正啟動機器的功能。
這裡為了描述它們之間的關系,把主機闆畫到了機箱的外面。如果連接配接線連接配接到不同的主機闆,那麼真正執行按鈕請求的主機闆也就不同了,而客戶是不知道這些變化的。
通過引入按鈕和連接配接線,來讓發出指令的客戶和指令的真正實作者——主機闆完全解耦,客戶操作的始終是按鈕,按鈕後面的事情客戶就統統不管了。
要用程式來解決上面提出的問題,一種自然的方案就是來模拟上述解決思路。
在指令模式中,會定義一個指令的接口,用來限制所有的指令對象,然後提供具體的指令實作,每個指令實作對象是對用戶端某個請求的封裝,對應于機箱上的按鈕,一個機箱上可以有很多按鈕,也就相當于會有多個具體的指令實作對象。
在指令模式中,指令對象并不知道如何處理指令,會有相應的接收者對象來真正執行指令。就像電腦的例子,機箱上的按鈕并不知道如何處理功能,而是把這個請求轉發給主機闆,由主機闆來執行真正的功能,這個主機闆就相當于指令模式的接收者。
在指令模式中,指令對象和接收者對象的關系,并不是與生俱來的,需要有一個裝配的過程,指令模式中的Client對象就來實作這樣的功能。這就相當于在電腦的例子中,有了機箱上的按鈕,也有了主機闆,還需要有一個連接配接線把這個按鈕連接配接到主機闆上才行。
指令模式還會提供一個Invoker對象來持有指令對象,就像電腦的例子,機箱上會有多個按鈕,這個機箱就相當于指令模式的Invoker對象。這樣一來,指令模式的用戶端就可以通過Invoker來觸發并要求執行相應的指令了,這也相當于真正的客戶是按下機箱上的按鈕來操作電腦一樣。
2.2 模式結構和說明
指令模式的結構如圖2所示:
圖2 指令模式結構圖
Command:
定義指令的接口,聲明執行的方法。
ConcreteCommand:
指令接口實作對象,是“虛”的實作;通常會持有接收者,并調用接收者的功能來完成指令要執行的操作。
Receiver:
接收者,真正執行指令的對象。任何類都可能成為一個接收者,隻要它能夠實作指令要求實作的相應功能。
Invoker:
要求指令對象執行請求,通常會持有指令對象,可以持有很多的指令對象。這個是用戶端真正觸發指令并要求指令執行相應操作的地方,也就是說相當于使用指令對象的入口。
Client:
建立具體的指令對象,并且設定指令對象的接收者。注意這個不是我們正常意義上的用戶端,而是在組裝指令對象和接收者,或許,把這個Client稱為裝配者會更好了解,因為真正使用指令的用戶端是從Invoker來觸發執行。
2.3 指令模式示例代碼
(1)先來看看指令接口的定義,示例代碼如下:
public interface Command { public void execute(); } |
(2)再來看看具體的指令實作對象,示例代碼如下:
public class ConcreteCommand implements Command { private Receiver receiver = null; private String state; public ConcreteCommand(Receiver receiver){ this.receiver = receiver; } public void execute() { //通常會轉調接收者對象的相應方法,讓接收者來真正執行功能 receiver.action(); } } |
(3)再來看看接收者對象的實作示意,示例代碼如下:
public class Receiver { public void action(){ //真正執行指令操作的功能代碼 } } |
(4)接下來看看Invoker對象,示例代碼如下:
public class Invoker { private Command command = null; public void setCommand(Command command) { this.command = command; } public void runCommand() { //調用指令對象的執行方法 command.execute(); } } |
(5)再來看看Client的實作,注意這個不是我們通常意義上的測試用戶端,主要功能是要建立指令對象并設定它的接收者,是以這裡并沒有調用執行的代碼,示例代碼如下:
public class Client { public void assemble(){ //建立接收者 Receiver receiver = new Receiver(); //建立指令對象,設定它的接收者 Command command = new ConcreteCommand(receiver); //建立Invoker,把指令對象設定進去 Invoker invoker = new Invoker(); invoker.setCommand(command); } } |
2.4 使用指令模式來實作示例
要使用指令模式來實作示例,需要先把指令模式中所涉及的各個部分,在實際的示例中對應出來,然後才能按照指令模式的結構來設計和實作程式。根據前面描述的解決思路,大緻對應如下:
·機箱上的按鈕就相當于是指令對象
·機箱相當于是Invoker
·主機闆相當于接收者對象
·指令對象持有一個接收者對象,就相當于是給機箱的按鈕連上了一根連接配接線
·當機箱上的按鈕被按下的時候,機箱就把這個指令通過連接配接線發送出去。
主機闆類才是真正實作開機功能的地方,是真正執行指令的地方,也就是“接收者”。指令的實作對象,其實是個“虛”的實作,就如同那根連接配接線,它哪知道如何實作啊,還不就是把指令傳遞給連接配接線連到的主機闆。
使用指令模式來實作示例的結構如圖3所示:
圖3 使用指令模式來實作示例的結構示意圖
還是來看看示例代碼,會比較清楚。
(1)定義主機闆
根據前面的描述,我們會發現,真正執行客戶指令或請求的是主機闆,也隻有主機闆才知道如何去實作客戶的指令,是以先來抽象主機闆,把它用對象描述出來。
先來定義主機闆的接口,最起碼主機闆會有一個能開機的方法,示例代碼如下:
public interface MainBoardApi { public void open(); } |
定義了接口,那就接着定義實作類吧,定義兩個主機闆的實作類,一個是技嘉主機闆,一個是微星主機闆,現在的實作是一樣的,但是不同的主機闆對同一個指令的操作可以是不同的,這點大家要注意。由于兩個實作基本一樣,就示例一個,示例代碼如下:
public class GigaMainBoard implements MainBoardApi{ public void open(){ System.out.println("技嘉主機闆現在正在開機,請等候"); System.out.println("接通電源......"); System.out.println("裝置檢查......"); System.out.println("裝載系統......"); System.out.println("機器正常運轉起來......"); System.out.println("機器已經正常打開,請操作"); } } |
微星主機闆的實作和這個完全一樣,隻是把技嘉改名成微星了。
(2)定義指令接口和指令的實作
對于客戶來說,開機就是按下按鈕,别的什麼都不想做。把使用者的這個動作抽象一下,就相當于客戶發出了一個指令或者請求,其它的客戶就不關心了。為描述客戶的指令,現定義出一個指令的接口,裡面隻有一個方法,那就是執行,示例代碼如下:
public interface Command { public void execute(); } |
有了指令的接口,再來定義一個具體的實作,其實就是模拟現實中機箱上按鈕的功能,因為我們按下的是按鈕,但是按鈕本身是不知道如何啟動電腦的,它需要把這個指令轉給主機闆,讓主機闆去真正執行開機功能。示例代碼如下:
public class OpenCommand implements Command{ private MainBoardApi mainBoard = null; public OpenCommand(MainBoardApi mainBoard) { this.mainBoard = mainBoard; } public void execute() { //對于指令對象,根本不知道如何開機,會轉調主機闆對象 //讓主機闆去完成開機的功能 this.mainBoard.open(); } } |
由于客戶不想直接和主機闆打交道,而且客戶根本不知道具體的主機闆是什麼,客戶隻是希望按下啟動按鈕,電腦就正常啟動了,就這麼簡單。就算換了主機闆,客戶還是一樣的按下啟動按鈕就可以了。
換句話說就是:客戶想要和主機闆完全解耦,怎麼辦呢?
這就需要在客戶和主機闆之間建立一個中間對象了,客戶發出的指令傳遞給這個中間對象,然後由這個中間對象去找真正的執行者——主機闆,來完成工作。
很顯然,這個中間對象就是上面的指令實作對象,請注意:這個實作其實是個虛的實作,真正的實作是主機闆完成的,在這個虛的實作裡面,是通過轉調主機闆的功能來實作的,主機闆對象執行個體,是從外面傳進來的。
(3)提供機箱
客戶需要操作按鈕,按鈕是放置在機箱之上的,是以需要把機箱也定義出來,示例代碼如下:
public class Box { private Command openCommand; public void setOpenCommand(Command command){ this.openCommand = command; } public void openButtonPressed(){ //按下按鈕,執行指令 openCommand.execute(); } } |
(4)客戶使用按鈕
抽象好了機箱和主機闆,指令對象也準備好了,客戶想要使用按鈕來完成開機的功能,在使用之前,客戶的第一件事情就應該是把按鈕和主機闆組裝起來,形成一個完整的機器。
在實際生活中,是由裝機工程師來完成這部分工作,這裡為了測試簡單,直接寫在用戶端開頭了。機器組裝好過後,客戶應該把與主機闆連接配接好的按鈕對象放置到機箱上,等待客戶随時操作。把這個過程也用代碼描述出來,示例代碼如下:
public class Client { public static void main(String[] args) { //1:把指令和真正的實作組合起來,相當于在組裝機器, //把機箱上按鈕的連接配接線插接到主機闆上。 MainBoardApi mainBoard = new GigaMainBoard(); OpenCommand openCommand = new OpenCommand(mainBoard); //2:為機箱上的按鈕設定對應的指令,讓按鈕知道該幹什麼 Box box = new Box(); box.setOpenCommand(openCommand); //3:然後模拟按下機箱上的按鈕 box.openButtonPressed(); } } |
運作一下,看看效果,輸出如下:
技嘉主機闆現在正在開機,請等候 接通電源...... 裝置檢查...... 裝載系統...... 機器正常運轉起來...... 機器已經正常打開,請操作 |
你可以給指令對象組裝不同的主機闆實作類,然後再次測試,看看效果。
事實上,你會發現,如果對象結構已經組裝好了過後,對于真正的用戶端,也就是真實的使用者而言,任務就是面對機箱,按下機箱上的按鈕,就可以執行開機的指令了,實際生活中也是這樣的。
(5)小結
如同前面的示例,把客戶的開機請求封裝成為一個 OpenCommand 對象,客戶的開機操作就變成了執行 OpenCommand 對象的方法了?如果還有其它的指令對象,比如讓機器重新開機的 ResetCommand 對象;那麼客戶按下按鈕的動作,就可以用這不同的指令對象去比對,也就是對客戶進行參數化。
用大白話描述就是:客戶按下一個按鈕,到底是開機還是重新開機,那要看參數化配置的是哪一個具體的按鈕對象,如果參數化的是開機的指令對象,那就執行開機的功能,如果參數化的是重新開機的指令對象,那就執行重新開機