Command Pattern Written by Tianyapiao
1.定義
? ? ? ?將一個請求封裝為一個對象(即我們創(chuàng)建的Command對象),從而使你可用不同的請求對客戶進行參數化; 對請求排隊或記錄請求日志,以及支持可撤銷的操作。命令模式是一種對象行為型模式,其別名為動作(Action)模式或事物(Transaction)模式。
? ? ? ?相信大家看完命令模式定義的時候是一臉懵逼的,其實我也和你們一樣。但是我們如果能聯系下生活或者我們所學過的知識,那么理解起來就應該很容易了。下面就跟著我來探討一下命令模式吧。
? ? ? ?學過Java的童鞋們應該都配過jdk環(huán)境變量(jdk的環(huán)境配置在這里我們不做討論),配置成功后,在cmd命令提示符中輸入java -version會彈出jdk的具體版本,輸入javac則會彈出javac相關的命令操作,具體是什么我們來看圖:
? ? ? ?在這里我們可以將javac命令理解成一個請求的發(fā)送者,用戶通過它來發(fā)送一個“查看jdk環(huán)境是否配置成功的命令”,而jdk是請求的最終接收者和處理者,javac和java version "1.8.0_131"之間并不存在直接耦合的關系,它們通過JAVA_HOME,CLASSPATH,Path這三個環(huán)境變量的配置連接在一起,使得cmd最終輸出了java version "1.8.0_131",如果我們沒配置,或者配置不成功,那么它們兩個完全沒有任何關系,cmd也會返回“javac不是內部或外部命令”。
? ? ? ?說了這么多 ,我們來看看命令模式需要解決哪些問題呢?請看下文:
2. 解決的問題
? ? ? 在軟件系統中,行為請求者與行為實現者通常是一種緊耦合的關系,但某些場合,比如需要對行為進行記錄、撤銷或重做、事務等處理時,這種無法抵御變化的緊耦合的設計就不太合適。
3.命令模式的類圖
4.根據上面的類圖我們很容易得到以下代碼
//1.抽象命令類,用來聲明執(zhí)行操作的接口
public interface Command{
? ? ? ?void? execute();
}
//2.具體命令類,實現具體命令。
public class ConcereteCommand implements Command{
//具體命令類包含有一個接收者,將這個接收者對象綁定于一個動作
? ????? private Receiver receiver;
? ? ????public? ConcereteCommand(Receiver receiver){
? ? ? ? ? ?????this.receiver =receiver;
? ? ? ? }?????
? ? ?//說這個實現是“虛”的,因為它是通過調用接收者相應的操作來實現execute方法? ? ??
??????public void execute(){
? ? ? ????????? receiver.action();
? ? ????}
}
//3.請求接收者類,知道如何實施與執(zhí)行一個請求相關的操作,任何類都可能作為一個接收者。
public class Receiver{
????//真正的命令實現??
?????public void action(){
? ????????????System.out.println("Execute request!");
????????}
}
///請求調用者類,要求該命令執(zhí)行這個請求
public class Invoker{
????private Command command;
? ? // 設置命令????public void SetCommand(Command command){
????????this.command =command;
????}? ? ?//執(zhí)行命令? ????public void executeCommand(){
? ? ? ? command.execute();
?????}
}
//客戶端代碼
public class? Client{
????public static void main(string[] args)
????{
????????Receiver receiver=new Receiver();
? ? ? ? Command command=new ConcereteCommand(receiver);
????????Invoker invoker=new Invoker();
????????invoker.SetCommand(command);
????????invoker.executeCommand();
????}
}
在IntelliJ IDEA 2017.1 x64軟件中的測試結果如下:
5.應用舉例
Sunny軟件公司開發(fā)人員使用命令模式來設計“自定義功能鍵模塊”,其核心結構如下圖:
? ? ? 在圖中,FBSettingWindow是“功能鍵設置界面類”,FunctionButton充當請求調用者,Command充當抽象命令類,MinimizeCommand和HelpCommand充當具體命令類,WindowHandler和HelpHandler充當請求接收者。完整代碼如下:
//1.抽象命令類
public abstract classCommand {
? ? ? ?public abstract voidexecute();
}
//2.具體命令類---幫助命令類
public class HelpCommand extends Command{
????private HelpHandler hhObj;//維持對請求接收者的引用
????public HelpCommand() {
????????hhObj=newHelpHandler();
????}
????//命令執(zhí)行方法,將調用請求接收者的業(yè)務方法
????@Override
????public void execute() {
????????hhObj.display();
????}
}
//3.具體命令類--最小化窗口命令類
public class MinimizeCommand extends Command{
????private WindowHandler whObj;//維持對請求接收者的引用
????public MinimizeCommand() {
????whObj=newWindowHandler();
}
????//執(zhí)行命令方法,將調用請求接收者的業(yè)務方法
????@Override
????public void execute() {
????????whObj.minimize();
????}
}
//4.窗口處理類:請求接收者
public class WindowHandler {
????public voidminimize(){
????????System.out.println("將窗口最小化至托盤!");
????}
}
//5.幫助文檔處理類:請求接收者
public class HelpHandler {
????public void display(){
????????System.out.println("顯示幫助文檔!");
? ? ?}
}
//6..功能鍵設置窗口類
import java.util.ArrayList;
//功能鍵設置窗口類
public class FBSettingWindow{
????private String title;
????//定義一個ArrayList集合來存儲所有的功能鍵
????private ArrayList<FunctionButton>??fbs=newArrayList<>();
????public String getTitle() {
????????return title;
????}
????public void setTitle(String title) {
????????this.title= title;
????}
????public ArrayList<FunctionButton> getFb() {
????returnfbs;
????}
????public void setFb(ArrayList<FunctionButton> fbs) {
????????this.fbs= fbs;
????}
????public FBSettingWindow(String title) {
????????this.title= title;
????}
????public void addFunctionButton(FunctionButton fb){
????????fbs.add(fb);
????}
????public void removeFunctionButton(FunctionButton fb){
????????fbs.remove(fb);
????}
????//7.顯示窗口及功能鍵
????public void display(){
????????System.out.println("顯示窗口:"+title);
????????System.out.println("顯示功能鍵:");
????????for(Object obj:fbs){
????????????????System.out.println(((FunctionButton) obj).getName());
????????}
????????????????System.out.println("----------------------------");
????}
}
//8.功能鍵類:請求 的發(fā)送者
public class FunctionButton {
????private String name;
? ? private Command command;
????public FunctionButton(String name) {
????????this.name= name;
????}
????public String getName() {
? ????????return name;
? ? }
????//為功能鍵注入命令
? ? public void setCommand(Command command) {
????????this.command= command;
????}????
????//發(fā)送請求的方法
????public void onClick(){
????????System.out.print("點擊功能鍵:");
????????command.execute();
????}
}
//9.配置文件config.xml
<?xml version="1.0" encoding="UTF-8"?>
<config>
????<type>HelpCommand</type>
????<type>MinimizeCommand</type>
</config>
//10.xml配置文件解析類,此處我使用的是dom解析
注:使用下列代碼需要導入dom4j-1.6.1.jar?
下載地址? ?鏈接:http://pan.baidu.com/s/1geC6xMF? 密碼:e8mf
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.File;
import java.util.List;
public class XMLUtil{
public static Object getBean(inti){
try{
????//創(chuàng)建saxReader對象
????SAXReader reader=new SAXReader();
????//通過read方法讀取一個文件 轉換成Document對象
????Document doc=reader.read(newFile("src/config.xml"));
????//獲取根節(jié)點元素對象
????Element rootNode=doc.getRootElement();//得到了config
????String type =null;
????//獲取根元素節(jié)點下 所有元素的子節(jié)點
????List<Element> elements = rootNode.elements();
????if(0==i){
????????type= elements.get(0).getText();
????}else{
????????type= elements.get(1).getText();
????}
????Class c=Class.forName(type);
????Object obj=c.newInstance();
????return obj;
????}catch(Exception e) {
????????e.printStackTrace();
????????return null;
????????}
? ?}
}
//客戶端
public class Client {
????public static void main(String args[]){
????FBSettingWindow fbsw=new FBSettingWindow("功能鍵設置");
????FunctionButton fb1,fb2;
? ? fb1=new FunctionButton("功能鍵1");
????fb2=newFunctionButton("功能鍵2");
????Command cmd1,cmd2;
????//通過讀取配置文件和反射生成具體命令對象
????cmd1=(Command)XMLUtil.getBean(0);
????cmd2=(Command)XMLUtil.getBean(1);
????//將命令注入到功能鍵
????fb1.setCommand(cmd1);
????fb2.setCommand(cmd2);
????//Ctrl+D? 復制一行代碼(只適用于idea)
????fbsw.addFunctionButton(fb1);
????fbsw.addFunctionButton(fb2);
????fbsw.display();
????//調用功能鍵的業(yè)務方法
????fb1.onClick();
????fb2.onClick();
????}
}
//idea中的運行結果和項目結構圖如下:
6.適用場景
1.?命令的發(fā)送者和命令執(zhí)行者有不同的生命周期。命令發(fā)送了并不是立即執(zhí)行。
2. 命令需要進行各種管理邏輯。
3.?需要支持撤消\重做操作(這種狀況的代碼大家可以上網搜索下,有很多,這里不進行詳細解讀)。
7.結論:
通過對上面的分析我們可以知道如下幾點:
1. 命令模式是通過命令發(fā)送者和命令執(zhí)行者的解耦來完成對命令的具體控制的。
2.?命令模式是對功能方法的抽象,并不是對對象的抽象。
3.?命令模式是將功能提升到對象來操作,以便對多個功能進行一系列的處理以及封裝。
好了,我對命令模式的理解到這里就結束了,如果大家發(fā)現有什么錯誤的地方,希望能不吝指正。如果有疑問的地方也可以提出,共同進步。
如果覺得文章對你有幫助,請點個贊嘍,嘿嘿!
附:為了幫助大家更好的理解代碼,我已經把代碼上傳到github上,下面附上代碼鏈接