JAVA 设计模式—————— 命令模式和职责链模式
學習 Netty 前的準備工作!!
這里,還是先來復習一下 OOP (面向對象)的七大原則:
- 開閉原則:對擴展開放,對修改關閉
- 里氏替換原則:繼承必須確保超類所擁有的性質在子類中仍然成立。
- 依賴倒置原則:要面向接口編程,不要面向實現編程
- 單一職責原則:要控制類的粒度大小,將對象解耦,提高其內聚性
- 接口隔離原則:要為各個類創建它們需要的專用接口
- 迪米特法則:盡量先使用合并或者聚合等關聯關系來實現,其次才考慮使用繼承關系來實現
命令模式(詳解版)
在軟件開發系統中,方法的請求者 與 方法的實現者之間經常存在著緊密的耦合關系,這不利于軟件功能的擴展與維護。例如:想對方法進行 撤銷、重做、記錄 等處理都很不方便,因此,如何將方法的請求者與方法的實現者解耦,變得很重要,命令模式由此而生。
在現實生活中,命令模式的例子有很多,比如看電視的時候,我們只需要輕輕一按遙控器就能完成頻道的切換,這就是命令模式,將換臺請求和換臺處理完全解耦了。電視機遙控器(命令發生者)通過按鈕(具體命令)來遙控電視機(命令接收者)。
再比如,我們去餐廳吃飯,菜單不是等到客人來了之后才定制的,而是已經預先配置好的。這樣,客人來了就只需要點菜,而不是由客人臨時定制。餐廳提供的菜單就相當于把請求和處理進行了解耦,這就是命令模式的體現。
命令模式的定義與特點
命令(Command)模式的定義如下:將一個請求封裝為一個對象。將發出請求的責任和執行請求的責任分隔開。這樣兩者之間通過命令對象進行溝通,這樣方便將命令對象進行儲存、傳遞、調用、增加與管理。
命令模式的主要優點:
- 通過引入中間件(抽象接口)降低系統的耦合度。
- 擴展性良好,增加或者刪除命令非常方便。采用命令模式增加與刪除命令不會影響其他類,其滿足開閉原則。
- 可以實現宏命令。命令模式可以與組合模式結合,將多個命令裝配成一個組合命令,即宏命令。
- 方便實現 Undo 和 Redo 操作。命令模式可以與后面介紹的備忘錄模式結合,實現命令的撤銷與恢復。
- 可以再現有命令的基礎上,增加額外的功能。比如日志記錄,集合裝飾器模式會更加靈活。
其缺點是:
- 可能產生大量具體的命令類。因為每一個具體操作都需要設計一個具體命令類,這會增加系統的復雜性。
- 命令模式的結果其實就是接收方的執行結果,但是為了以命令的形式進行架構、解耦請求與實現,引入了額外的類型結構(引入了請求方與抽象命令接口),增加了理解上的困難。不過這也是設計模式的通病,抽象必然會額外導致類的數量,代碼抽離肯定比代碼聚合更加難理解。
命令模式的結構與實現
可以將系統中的相關操作抽象成命令,使調用者與實現者相關分離,其結構如下:
1、命令模式的結構
命令模式包含以下主要角色:
- 抽象命令類(Command)角色:聲明執行命令的接口,擁有執行命令的抽象方法 execute()。
- 具體命令類(Concrete Command)角色:是抽象命令類的具體實現類,它擁有接收者對象,并通過調用接收者的功能來完成命令要執行的操作。
- 實現者 / 接收者(Receiver)角色:執行命令功能的相關操作,是具體命令對象業務的真正實現者。
- 調用者 / 請求者(Invoker)角色:是請求的發送者,它通常擁有很多的命令對象,并通過訪問命令對象來執行相關請求,它不直接訪問接收者。
模式的實現:
/*** @author wcc* @date 2021/11/22 10:30* 命令模式的實現*/ public class CommandPattern {public static void main(String[] args) {Receiver receiver = new Receiver();Command command = new ConcreteCommand(receiver);Invoker invoker = new Invoker(command);System.out.println("客戶訪問調用者的call方法");invoker.call();}}// 調用者 class Invoker {private Command command;public Invoker(Command command) {this.command = command;}public void setCommand(Command command) {this.command = command;}public void call(){System.out.println("調用者執行命令 Command...");command.execute();} }// 抽象命令 interface Command {public abstract void execute(); }// 具體命令 class ConcreteCommand implements Command {private Receiver receiver;public ConcreteCommand(Receiver receiver) {this.receiver = receiver;}@Overridepublic void execute() {receiver.action();}}// 接收者 class Receiver {public void action() {System.out.println("接收者的action()方法被調用...");} }測試運行結果如下:
命令模式的應用實例
【例1】:用命令模式實現客戶區餐館吃早餐的實例
分析:客戶去餐館可選擇的早餐有腸粉、河粉和混沌等,客戶可以向服務員選擇以上早餐中的若干種,服務員將客戶的請求交給相關的廚師去做。這里的早餐相當于命令,服務員相當于調用者,廚師相當于接收者,所以用命令模式比較合適。
- 首先,定義一個早餐類(Breakfast),它是抽象命令類,有抽象方法 cooking(),說明要做什么
- 再定義其子類腸粉(ChangFen)、餛飩類(HunTun)和河粉類(HeFen),它們是具體命令類,實現早餐類的 cooking() 方法,但是它們不會具體做,而是交給具體的廚師去做;
- 具體廚師類有腸粉廚師(ChangFenChef)、餛飩廚師(HunTunChef)和河粉廚師(HeFenCef),它們是命令的接收者。
最后,定義服務員類( Waiter),它接收客戶的做菜請求,并發出做菜的命令。客戶類是通過服務員類來實現點菜的
/*** @author wcc* @date 2021/11/22 11:02*/ public class CookingCommand {public static void main(String[] args) {BreakFast food1 = new ChangFen();BreakFast food2 = new HUnTun();BreakFast food3 = new HeFen();Waiter waiter = new Waiter();waiter.setChangFen(food1);waiter.setHUnTun(food2);waiter.setHeFen(food3);waiter.chooseChangFen();waiter.chooseHunTun();waiter.chooseHeFen();}}// 調用者 服務員 class Waiter {private BreakFast ChangFen,HUnTun,HeFen;public Waiter() {}public void setChangFen(BreakFast changFen) {ChangFen = changFen;}public void setHUnTun(BreakFast HUnTun) {this.HUnTun = HUnTun;}public void setHeFen(BreakFast heFen) {HeFen = heFen;}public void chooseChangFen(){ChangFen.cooking();}public void chooseHunTun(){HUnTun.cooking();}public void chooseHeFen(){HeFen.cooking();} }// 抽象命令:早餐 interface BreakFast {public abstract void cooking(); }// 具體命令:腸粉 class ChangFen implements BreakFast {private ChangFenChef changFenChef = new ChangFenChef();@Overridepublic void cooking() {changFenChef.cooking();}}// 具體命令:餛飩 class HUnTun implements BreakFast {private HunTunChef hunTunChef = new HunTunChef();@Overridepublic void cooking() {hunTunChef.cooking();}}// 具體命令:腸粉 class HeFen implements BreakFast {private HeFenChef heFenChef = new HeFenChef();@Overridepublic void cooking() {heFenChef.cooking();}}// 接收者:腸粉廚師 class ChangFenChef {public void cooking(){System.out.println("做腸粉...");}}// 接收者:餛飩廚師 class HunTunChef {public void cooking() {System.out.println("做餛飩...");}}// 接收者:河粉廚師 class HeFenChef {public void cooking() {System.out.println("做河粉...");}}命令模式的應用場景
當系統的某項操作具備命令語義,且命令實現不穩定(變化)的時候,可以通過命令模式解耦請求與實現。使用抽象命令接口使請求方的代碼架構穩定,封裝接收方具體命令的實現細節。接收方與抽象命令呈現弱耦合(內部方法無需一致),具備良好的擴展性。
命令模式通常適用于以下場景:
- 請求調用者需要與請求接收者解耦的時候,命令模式可以使得調用者和接收者不直接交互。
- 系統隨機請求命令或者經常增加、刪除命令的時候,命令模式可以方便的實現這些功能。
- 當系統需要執行一系列操作的時候,命令模式可以定義宏命令來實現該功能。
- 當系統需要支持命令的撤銷(Undo)操作恢復(Redo)操作的時候,可以將命令對象存儲起來,采用備忘錄模式來實現。
責任鏈模式(職責鏈模式)詳解
在現實生活中,一個事件需要經過多個對象處理是很常見的場景。例如:采購審批流程、請假流程等。公司員工請假,可批假的有領導有部門有負責人、副總經理、總經理等,但是每個領導能批準的天數不同,員工必須根據需要請假的天數去找不同的領導簽名,也就是說員工必須記住每個領導的姓名、電話和地址等信息,這無疑增加了難度。
在計算機軟硬件中也有相關的例子,如總線網中數據報傳送,每臺計算機格局目標地址是否同自己的地址相同來決定是否接收;還有異常處理中,處理程序根據異常的類型決定自己是否處理該異常;還有 Struts2 的攔截器、JSP 和 Servlet 的Filter等,所有這些,都可以考慮使用責任鏈模式來實現。
責任鏈模式的定義與特點
責任鏈(Chain Of Responsibility)模式 的定義:為了避免請求發送者與多個請求處理者耦合在一起,于是將所有請求的處理者通過前一對象記住其下一個對象的引用而連成一條鏈;當有請求發生的時候,可將請求沿著這條鏈傳遞,直到有對象處理它為止。
注意:責任鏈模式也叫做職責鏈模式。
在責任鏈模式中,客戶只需要將請求發送到責任鏈上即可,無須關心請求的處理細節和請求的傳遞過程,請求會自動進行傳遞。所以責任鏈將請求的發送者和請求的處理者解耦了。
責任鏈模式是一種對象行為型模式,其主要優點如下:
- 降低了對象之間的耦合度。該模式使得一個對象無須直到到底是哪一個對象處理其請求以及鏈的結構,發送者和接收者也無需擁有對方的明確信息。
- 增強了系統的可擴展性。可以根據需要增加新的請求處理類,滿足開閉原則。
- 增強了給對象指派職責的靈活性。當工作流程發生變化,可以動態的改變鏈內的成員或者調動它們的次序,也可動態的新增或者刪除責任。
- 責任鏈簡化了對象之間的連接。每個對象只需要保持一個指向其后繼者的引用,不需要保持其他所有處理者的引用,這避免了使用眾多的 if 或者 if … else 語句。
- 責任分擔。每個類只需要處理自己該處理的工作,不該處理的傳遞給下一個對象完成,明確各類的責任范圍,符合類的單一職責原則。
其主要缺點如下:
- 不能保證每個請求一定被處理。由于一個請求沒有明確的接收者,所以不能保證它一定會被處理,該請求可能一直傳到鏈的末端都得不到處理。
- 對比較長的職責鏈,請求的處理可能涉及到多個處理對象,系統性能將受到一定的影響。
- 職責鏈建立的合理性要靠客戶端來保證,增加了客戶端的復雜性,可能會由于職責鏈的錯誤設置而導致系統出錯,如可能會造成循環調用。
職責鏈模式的結構與實現
通常情況下,可以通過數據鏈表來實現職責鏈模式的數據結構。
1、職責鏈模式的結構
職責鏈模式主要包含以下角色:
- 抽象處理者(Handler)角色:定義一個處理請求的接口,包含抽象處理方法和一個后繼連接。
- 具體處理者(Concrete Handler)角色:實現抽象處理者的處理方法,判斷能否處理本次請求,如果可以處理請求則處理,否則將該請求轉給它的后繼者。
- 客戶類(Client)角色:創建處理鏈,并向鏈頭的具體處理者對象提交請求,它不關心處理細節和請求的傳遞過程。
責任鏈模式的本質是解耦請求與處理,讓請求在處理鏈中能進行傳遞與被處理;理解責任鏈模式應當理解其模式,而不是其具體實現。責任鏈模式的獨到之處是將其節點處理者組合成了鏈式結構,并允許節點自身決定是否進行請求處理或者轉發,相當于讓請求流動i起來。
結構圖:
]
職責鏈模式的實現:
/*** @author wcc* @date 2021/11/22 13:46*/ public class ChainResponsibilityPattern {public static void main(String[] args) {// 組裝責任鏈Handler handler1 = new ConcreteHandler1();Handler handler2 = new ConcreteHandler2();handler1.setNext(handler2);// 提交請求handler1.handleRequest("two");}}// 抽象處理者角色 abstract class Handler {private Handler next;public void setNext(Handler next) {this.next = next;}public Handler getNext() {return next;}// 處理請求的方法public abstract void handleRequest(String request); }// 具體處理者角色1 class ConcreteHandler1 extends Handler {public void handleRequest(String request){if(request.equalsIgnoreCase("one")){System.out.println("具體處理者1負責處理該請求...");}else{if(getNext()!=null){getNext().handleRequest(request);}else{System.out.println("沒有人處理該請求...");}}} }// 具體處理者角色2 class ConcreteHandler2 extends Handler {public void handleRequest(String request){if(request.equalsIgnoreCase("two")){System.out.println("具體處理者2負責處理該請求...");}else{if(getNext()!=null){getNext().handleRequest(request);}else{System.out.println("沒有人處理該請求...");}}} }測試結果如下:
在上面代碼中,我們吧消息硬編碼為 String 類型的,而在真實業務中,消息是具備多樣性的,可以是 int、String 或者自定義類型。因此,在上面代碼的基礎上,可以對消息類型進行抽象 Request,增強了消息的兼容性。
職責鏈模式的應用實例
【例1】:用責任鏈模式設計一個請假審批模塊。
分析:加入規定學生請假小于或者等于兩天,班主任可以批準,小于或者等于7天,系主任可以批準;小于等于10天,院長可以批準;其他情況不予批準;這個實例適合使用職責鏈模式實現。
首先,定義一個領導類(Leader),它是抽象處理者,包含了一個指向下一位領導的指針 next 和一個處理假條的抽象處理方法 handleRequest(int LeaveDays);然后,定義班主任類(ClassAdviser)、系主任類(DepartmentHead)和院長類( Dean),它們是抽象處理者的子類,是具體處理者,必須根據自己的權力去實現父類的 handleRequest(int LeaveDays)方法,如果無權處理就將假條交給下一位具體處理者,直到最后;客戶類負責創建處理鏈,并將假條交給鏈頭的具體處理者(班主任)。
/*** @author wcc* @date 2021/11/22 13:59*/ public class LeaveApprovalTest {public static void main(String[] args) {//組裝責任鏈Leader teacher1 = new ClassAdviser();Leader teacher2 = new DepartmentHead();Leader teacher3 = new Dean();//Leader teacher4=new DeanOfStudies();teacher1.setNext(teacher2);teacher2.setNext(teacher3);//teacher3.setNext(teacher4);//提交請求teacher1.handleRequest(8);}}// 抽象處理者:領導類 abstract class Leader {private Leader next;public Leader getNext() {return next;}public void setNext(Leader next) {this.next = next;}// 處理請求的方法public abstract void handleRequest(int leaveDays); }// 具體處理者1:班主任類 class ClassAdviser extends Leader {public void handleRequest(int LeaveDays) {if (LeaveDays <= 2) {System.out.println("班主任批準您請假" + LeaveDays + "天。");} else {if (getNext() != null) {getNext().handleRequest(LeaveDays);} else {System.out.println("請假天數太多,沒有人批準該假條!");}}} }// 具體處理者2:系主任 class DepartmentHead extends Leader {@Overridepublic void handleRequest(int leaveDays) {if(leaveDays <= 7){System.out.println("系主任批準您請假" + leaveDays + "天");}else{if(getNext() != null){getNext().handleRequest(leaveDays);}else{System.out.println("請假天數太多,沒有人批準該假條");}}}}//具體處理者3:院長類 class Dean extends Leader {public void handleRequest(int LeaveDays) {if (LeaveDays <= 10) {System.out.println("院長批準您請假" + LeaveDays + "天。");} else {if (getNext() != null) {getNext().handleRequest(LeaveDays);} else {System.out.println("請假天數太多,沒有人批準該假條!");}}} } //具體處理者4:教務處長類 class DeanOfStudies extends Leader {public void handleRequest(int LeaveDays) {if (LeaveDays <= 20) {System.out.println("教務處長批準您請假" + LeaveDays + "天。");} else {if (getNext() != null) {getNext().handleRequest(LeaveDays);} else {System.out.println("請假天數太多,沒有人批準該假條!");}}} }測試結果如下:
職責鏈模式的應用場景
- 多個對象可以處理一個請求,但具體由哪個對象處理該請求在運行時自動確定
- 可動態指定一組對象處理請求,或者添加新的處理者
- 需要在不明確指定請求處理者的情況下,向多個處理者中的一個提交請求
職責鏈模式的擴展
職責鏈模式存在以下兩種情況:
- 純的職責鏈模式:一個請求必須被某一個處理者對象所接收,且一個具體處理者對某個請求的處理只能采用以下兩種行為之一:自己處理(承擔責任);把責任推給下家處理。
- 不純的職責鏈模式:允許出現某一個具體處理者對象在承擔了請求的一部分責任后又將剩余的責任傳給下家的情況,且一個請求可以最終不被任何接收端對象所接收。
總結
以上是生活随笔為你收集整理的JAVA 设计模式—————— 命令模式和职责链模式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【Java 责任链模式实例】
- 下一篇: HTML学习---中文网页编码声明