工具篇-Java中的設計模式積累(二)

目錄

行爲型設計模式

  1. 模版模式
  2. 策略模式
  3. 命令模式
  4. 責任鏈模式
  5. 狀態模式
  6. 觀察者模式
  7. 中介者模式 
  8. 迭代者模式
  9. 訪問者模式
  10. 備忘錄模式
  11. 解釋器模式

 ------------------------------------------行爲型設計模式

1. 模版模式

爲何有模版模式:html

定義一個算法的骨架,而將一些步驟的實現延遲到子類中。模板方法使得子類能夠在不改變算法結構的狀況下,從新定義算法中某些步驟的具體實現;java

優勢: git

  • 模板方法模式經過把不變的行爲搬到父類,去除了子類中的重複代碼;
  • 子類實現算法的某些細節,有助於算法的擴展;
  • 父類調用子類實現的操做,經過子類擴展增長新的行爲,符合「開放-封閉原則」 

缺點:算法

  • 在模版模式中,子類執行的結果影響了父類的結果,會增長代碼閱讀的難度;數據庫

應用場景:設計模式

  • 多個子類存在共有方法,且邏輯相同;    
  • 重要、複雜的算法,能夠把核心算法設計爲模板方法

實現:瀏覽器

模版模式中,基類方法的默認實現被退化爲鉤子Hooks的概念,他們被設計在子類中被重寫,若是你指望一些方法在子類中不被重寫,可讓他們爲final。例如,安全

模版抽象類微信

 1 public abstract class HouseTemplate {
 2 
 3     protected HouseTemplate(String name){
 4         this.name = name;
 5     }
 6 
 7     protected String name;
 8 
 9     protected abstract void buildDoor();
10 
11     protected abstract void buildWindow();
12 
13     protected abstract void buildWall();
14 
15     protected abstract void buildBase();
16 
17     protected abstract void buildToilet();
18 
19     //鉤子方法
20     protected boolean isBuildToilet(){
21         return true;
22     }
23 
24     //公共邏輯
25     public final void buildHouse(){
26 
27         buildBase();
28         buildWall();
29         buildDoor();
30         buildWindow();
31         if(isBuildToilet()){ 
32             buildToilet();
33         }
34     }
35 
36 }

子類1網絡

 1 public class HouseOne extends HouseTemplate {
 2 
 3     HouseOne(String name){
 4         super(name);
 5     }
 6 
 7     HouseOne(String name, boolean isBuildToilet){
 8         this(name);
 9         this.isBuildToilet = isBuildToilet;
10     }
11 
12     public boolean isBuildToilet;
13 
14     @Override
15     protected void buildDoor() {
16         System.out.println(name +"的門要採用防盜門");
17     }
18 
19     @Override
20     protected void buildWindow() {
21         System.out.println(name + "的窗戶要面向北方");
22     }
23 
24     @Override
25     protected void buildWall() {
26         System.out.println(name + "的牆使用大理石建造");
27     }
28 
29     @Override
30     protected void buildBase() {
31         System.out.println(name + "的地基使用鋼鐵地基");
32     }
33 
34     @Override
35     protected void buildToilet() {
36         System.out.println(name + "的廁所建在東南角");
37     }
38 
39     @Override
40     protected boolean isBuildToilet(){
41         return isBuildToilet;
42     }
43 
44 }

子類2

 1 public class HouseTwo extends HouseTemplate {
 2 
 3     HouseTwo(String name){
 4         super(name);
 5     }
 6 
 7     @Override
 8     protected void buildDoor() {
 9         System.out.println(name + "的門採用木門");
10     }
11 
12     @Override
13     protected void buildWindow() {
14         System.out.println(name + "的窗戶要向南");
15     }
16 
17     @Override
18     protected void buildWall() {
19         System.out.println(name + "的牆使用玻璃製造");
20     }
21 
22     @Override
23     protected void buildBase() {
24         System.out.println(name + "的地基使用花崗岩");
25     }
26 
27     @Override
28     protected void buildToilet() {
29         System.out.println(name + "的廁所建在西北角");
30     }
31 
32 }

客戶端

 1 public class Clienter {
 2 
 3     public static void main(String[] args){
 4         HouseTemplate houseOne = new HouseOne("房子1", false);
 5         HouseTemplate houseTwo = new HouseTwo("房子2");
 6         houseOne.buildHouse();
 7         houseTwo.buildHouse();
 8     }
 9 
10 }

能夠看到,經過重寫鉤子方法自定義了房子1不須要建造廁所(fasle),另外能夠參考這篇文章:設計模式學習筆記之九:模板方法模式

2. 策略模式

爲何有策略模式:

Define a family of algorithms,encapsulate each one,and make them interchangeable.
定義一組算法(我以爲應該是一個行爲的不一樣實現吧),將每一個算法都封裝起來,而且使它們之間能夠互換。

通俗點講是利用繼承和多態機制,從而實現同一行爲在不一樣場景下的不一樣實現吧。

優勢: 

  • 算法能夠自由切換;
  • 避免使用多重條件判斷;
  • 擴展性良好,策略類遵頊里氏替換原則,增長一個策略只須要實現一個接口就能夠了。

缺點:

  • 策略模式形成不少的策略類,每一個具體策略類都會產生一個新類;
  • 全部的策略類都須要對外暴露,客戶端必須知道全部的策略類,並自行決定使用哪個策略類,違反了迪米特法,可以使用工廠方法模式、代理模式修整這個缺陷。

應用場景:

  • 若是在一個系統裏面有許多類,它們之間的區別僅在於它們的行爲,在運行時動態選擇具體要執行的行爲;
  • 在不一樣狀況下使用不一樣的策略(算法),或者策略還可能在將來用其它方式來實現;
  • 對客戶隱藏具體策略(算法)的實現細節,彼此徹底獨立。

實現:

策略模式有三種角色:

抽象策略角色這個是一個抽象的角色,一般使用接口或者抽象類去實現。好比Comparator接口;
具體策略角色包裝了具體的算法和行爲。就是實現了Comparator接口的實現一組實現類;
環境角色內部會持有一個抽象角色的引用,好比內部必定會有一個策略類的一個成員變量,這樣在構建函數中就能夠接收外部傳遞的具體的策略類。

例如,

    //抽象策略類 Strategy
    interface IStrategy {
        void algorithm();
    }

    //具體策略類 ConcreteStrategyA
    static class ConcreteStrategyA implements IStrategy {
        @Override
        public void algorithm() {
            System.out.println("Strategy A");
        }
    }

    //具體策略類 ConcreteStrategyB
    static class ConcreteStrategyB implements IStrategy {
        @Override
        public void algorithm() {
            System.out.println("Strategy B");
        }
    }

客戶端,

 1     //上下文環境角色
 2     static class Context {
 3         private IStrategy mStrategy; // 引用
 4 
 5         public Context(IStrategy strategy) {
 6             this.mStrategy = strategy;
 7         }
 8 
 9         public void algorithm() {
10             this.mStrategy.algorithm();
11         }
12     }

3. 命令模式

爲何有命令模式:

將一個請求封裝成一個對象,從而使請求參數化。

優勢: 

  • 下降"行爲請求者"與"行爲實現者"的耦合;
  • 新的命令能夠很容易添加到系統中。

缺點:

  • 可能會致使某些系統有過多的具體命令類。

應用場景:

在一些對行爲進行"記錄、撤銷/重作、事務"等處理的場合,好比:線程池、工做隊列、日誌請求等

  • 工做隊列:

在某一端添加命令,另外一端則是線程進行下面的動做:從隊列中取出一個命令,調用它的execute()方法,等待這個調用完成,而後將此命令對象丟棄,再取出下一個命令......這裏工做隊列和命令對象之間是徹底解耦的,線程可能此刻在進行財務運算,下一刻卻在讀取網絡數據。工做隊列對象不在意到底作些什麼,它們只知道取出命令對象,而後調用其execute()方法。相似地,它們只要實現命令模式的對象,就能夠放入隊列裏,當線程可用時,就調用此對象的execute()方法。

  • 日誌請求

多用於數據庫管理系統的實現,咱們須要把一系列的操做記錄下來(如寫在硬盤上),在遇到系統故障時讀出來以恢復數據,如何實現?利用對象的序列化把對象保存起來(記錄日誌),在須要的時候反序列化(恢復事務)。

注:這裏的可撤銷的操做就是:放棄該操做,回到未執行該操做前的狀態。

有兩種基本的思路來實現可撤銷的操做:
① 一種是補償式,又稱反操做式
好比被撤銷的操做是加的功能, 那撤消的實現就變成減的功能;好比被撤銷的操做是打開的功能,那麼撤銷的實現就變成關閉的功能。
② 另一種方式是存儲恢復式
意思就是把操做前的狀態記錄下來,而後要撤銷操做的時候就直接恢復回去就能夠了。

實現:

該模式具備如下角色:

抽象命令接口Command:定義接口,聲明執行的方法。
具體的命令對象ConcreteCommand:持有接受者對象,完成具體的命令。
接受者對象Receiver:接受者對象,真正執行命令的對象。
傳遞命令對象Invoker:調用者,要求命令對象執行請求。
客戶端對象Client:建立具體命令對象而且設置接受者。

好比:顧客來到餐館點一碗麪(發出請求) -> 櫃檯服務員記錄下來(建立命令) -> 服務員把訂單扔給廚房 -> 廚師很快作好了一碗麪(請求被執行)。顧客不知道將由誰來作這碗麪,櫃檯服務員也不知道,廚師不知道是誰點了這碗麪,其中,訂單就起解耦的做用。

首先,寫命令接口

1 public interface Command {
2     public abstract void execute();//只須要定義一個統一的執行方法
3 }

而後是執行者

1 public abstract class Chef {
2     //在此定義廚師的公共屬性
3     
4     //定義烹飪方法
5     public abstract void cook();
6 }

具體執行者,作面的廚師和作餅的廚師

1 public class NoodlesChef extends Chef{
2 
3     @Override
4     public void cook() {
5         System.out.println("作好了一碗美味的拉麪");
6     }
7 }
1 public class PieChef extends Chef{
2 
3     @Override
4     public void cook() {
5         System.out.println("作好了一塊香噴噴的大餅");
6     }
7 }

各類具體命令

 1 public class NoodlesCommand implements Command{
 2     private NoodlesChef chef;//專業作面的廚師
 3     
 4     public NoodlesCommand(){
 5         chef = new NoodlesChef();
 6     }
 7 
 8     @Override
 9     public void execute() {
10         chef.cook();
11         //調用其它須要的方法
12     }
13 }
 1 public class PieCommand implements Command{
 2     private PieChef chef;//專業作餅的廚師
 3     
 4     public PieCommand(){
 5         chef = new PieChef();
 6     }
 7 
 8     @Override
 9     public void execute() {
10         chef.cook();
11         //調用其它須要的方法
12     }
13 }

調用者invoker

 1 public class Waiter {
 2 
 3     private Command command;
 4 
 5     // 設置具體的命令
 6     public void setCommand(Command command) {
 7         this.command = command;
 8     }
 9 
10     // 執行命令
11     public void doCommand() {
12         command.execute();
13     }
14 }

客戶端,店館開張了

public class Test {    
    public static void main(String[] args) {
        System.out.println("第一位客戶X先生");
        System.out.println("X先生:你好,我須要一碗麪,我餓極了");
        NoodlesCommand nCmd = new NoodlesCommand();
        System.out.println("櫃檯服務員:好的廚房~~,接單");
        Waiter.doCommand(); //安排廚師作
                
        System.out.println("第二位客戶XX先生");
        System.out.println("XX先生:你好,我須要一塊餅,20分鐘後來取");
        PieCommand pCmd = new PieCommand();
        System.out.println("櫃檯服務員:好的廚房~~,接單");
        Waiter.doCommand(); // 安排廚師作
    }
}

4. 責任鏈模式

爲何有責任鏈模式:

搞一條對象鏈,這樣請求不用知道具體執行的是哪個對象,就實現了請求與對象之間的解耦。

優勢: 

  • 下降了對象之間的耦合度,上邊已經說了;
  • 加強了系統的可擴展性。能夠根據須要增長刪除請求處理類,知足開閉原則
  • 加強了給對象指派職責的靈活性。能夠動態地改變鏈內的成員的次序;
  • 責任分擔。每一個類只須要處理本身該處理的工做,符合類的單一職責原則

缺點(針對不純責任鏈模式):

  • 不能保證每一個請求必定被處理。,請求可能一直傳到鏈的末端都得不處處理;
  • 對比較長的職責鏈,系統性能將受到必定影響;
  • 職責鏈創建的合理性要靠客戶端來保證,可能會形成循環調用。

應用場景:

我以爲好多if/else,switch/case操做能夠考慮這個吧,仍是能充分利用這種模式的優勢唄,好比:

Netty 中的 Pipeline 和 ChannelHandler 經過責任鏈設計模式來組織代碼邏輯
Spring Security 使用責任鏈模式,能夠動態地添加或刪除責任(處理 request 請求)
Spring AOP 經過責任鏈模式來管理 Advisor

實現(具體參考:責任鏈模式妙用):

該模式具備如下角色:

抽象處理者(Handler):定義一個處理請求的接口(或抽象類),包含抽象處理方法和一個後繼鏈接

具體處理者(Concrete Handler):實現抽象處理者的處理方法,判斷可否處理本次請求,若是能夠則處理,不然將該請求轉給它的後繼者。

客戶類(Client):建立處理鏈,並向鏈頭的具體處理者對象提交請求,它不關心處理細節和請求的傳遞過程。

抽象接口

 1 public abstract class BaseCase { 
 2     // 爲 true 代表本身能夠處理該 case 
 3     private boolean isConsume; 
 4     public BaseCase(boolean isConsume) {   
 5         this.isConsume = isConsume; 
 6     }
 7      // 下一個責任節點
 8      private BaseCase nextCase; 
 9     public void setNextCase(BaseCase nextCase) {  
10         this.nextCase = nextCase; 
11     }     
12     public void handleRequest() {  
13         if (isConsume) {    
14             // 若是當前節點能夠處理,直接處理     
15             doSomething();  
16         } else {     
17             // 若是當前節點不能處理,而且有下個節點,交由下個節點處理     
18             if (null != nextCase) {     
19                 nextCase.handleRequest();    
20             } 
21         } 
22     } 
23     abstract protected void doSomething();
24 }
View Code

具體處理者,其中一個爲例

 1 public class OneCase extends BaseCase { 
 2     public OneCase(boolean isConsume) {   
 3         super(isConsume); 
 4     }
 5  
 6     @Override protected void doSomething() {   
 7     // TODO do something   
 8         System.out.println(getClass().getName()); 
 9     }
10 }
View Code

客戶端

1 String input = "1";      
2 OneCase oneCase = new OneCase("1".equals(input));   
3 TwoCase twoCase = new TwoCase("2".equals(input)); 
4 DefaultCase defaultCase = new DefaultCase(true); 
5 oneCase.setNextCase(twoCase); 
6 twoCase.setNextCase(defaultCase);      
7 oneCase.handleRequest();
View Code

5. 狀態模式

爲何有狀態模式:

讓一個對象在其內部狀態改變的時候改變其行爲

優勢: 

  • 將不一樣狀態的行爲分割開來,知足「單一職責原則」;
  • 將不一樣的狀態引入獨立的對象中會使狀態轉換更加明確,減小對象間的相互依賴;
  • 有利於擴展,經過定義新的子類很容易地增長新的狀態和轉換

缺點:

  • 每個狀態都對應一個子類,當你的狀態很是多的時候就會發生類膨脹。

應用場景:

行爲隨着狀態的改變而改變的時候,例如權限設計,人員的狀態不一樣即便執行相同的行爲結果也會不一樣,在這種狀況下須要考慮使用狀態模式。

實現:

該模式具備如下角色

環境(Context)角色:也稱爲上下文,它將與狀態相關的操做委託給當前狀態對象來處理;
抽象狀態(State)角色:接口,用來封裝對象中的特定狀態所對應的行爲;
具體狀態(Concrete State)角色:實現抽象狀態所對應的行爲。

好比:用戶登陸系統,同一個操做在不一樣狀態下會有不一樣的操做,先定義一個用戶狀態的接口

1 public interface LoginState {  
2     public void loadUser();
3     public void loginIn();
4     public void loginOut();
5 }

登陸成功狀態

 1 public class LoginInState implements LoginState{
 2 
 3     @Override
 4     public void loadUser() {
 5         // TODO Auto-generated method stub
 6         System.out.println("用戶數據載入成功");
 7 
 8     }
 9 
10     @Override
11     public void loginIn() {
12         // TODO Auto-generated method stub
13         System.out.println("已經登陸成功了");
14     }
15 
16     @Override
17     public void loginOut() {
18         // TODO Auto-generated method stub
19         System.out.println("已經登陸成功了,請按退出登陸按鈕");
20     }
21 
22 }
View Code

退出登陸狀態

 1 public class LoginErrorState implements LoginState{
 2     @Override
 3     public void loadUser() {
 4         // TODO Auto-generated method stub
 5         System.out.println("已經退出,請按登陸按鈕");
 6     }
 7 
 8     @Override
 9     public void loginIn() {
10         // TODO Auto-generated method stub
11         System.out.println("已經退出,請先登陸");
12     }
13 
14     @Override
15     public void loginOut() {
16         // TODO Auto-generated method stub
17         System.out.println("退出登陸成功");
18     }
19 }
View Code

用LoginContext管理這兩個狀態

 1 /**
 2  * 登陸環境上下文
 3  * @author ccj
 4  *
 5  */
 6 public class LoginContext {
 7     private LoginState loginState=new LoginErrorState();
 8 
 9     /**
10      * 能夠根據需求,進行單例化,工廠化
11      */
12     public void loginIn(){
13         loginState=new LoginInState();
14         loginState.loginIn();
15     }
16 
17     public void loginOut(){
18         loginState=new LoginErrorState();
19         loginState.loginOut();
20     }
21     
22     public void loadUser(){
23         loginState.loadUser();
24     }
25 }
View Code

客戶端按鍵

 1 public class Test {
 2     /**
 3      * 將登陸登出兩個動做, 分別對應兩個按鈕
 4      * @param args
 5      */
 6     public static void main(String[] args) {
 7         LoginContext context =new LoginContext();
 8         System.out.println("=======點擊登陸按鈕===>>=狀態:登陸=====>>選擇加載用戶數據===========");
 9         context.loginIn();
10         context.loadUser();
11     
12         System.out.println("=====點擊退出按鈕>>======狀態:退出=======>>選擇加載用戶數據=========");
13         context.loginIn();
14         context.loginOut();
15         context.loadUser();
16     }
17 }
View Code

測試結果

1 =======點擊登陸按鈕===>>=狀態:登陸=====>>選擇加載用戶數據===========
2 已經登陸成功了
3 用戶數據載入成功
4 =====點擊退出按鈕>>======狀態:退出=======>>選擇加載用戶數據=========
5 已經登陸成功了
6 退出登陸成功
7 已經退出,請按登陸按鈕

⚠️注意:在行爲受狀態約束的時候使用狀態模式,並且狀態不超過5個。

策略模式和狀態模式對比:

策略模式須要客戶端必須徹底知曉全部的策略方法,纔可以知道究竟哪個策略是當前須要的。而狀態模式客戶端在使用的時候,能夠沒必要關心有哪些狀態,他只須要去調用環境的行爲就能夠了,在環境的內部維護了這些狀態,就是策略模式服務的對象是不固定的,可是狀態模式服務的對象是固定的,每次都是那一個。

6. 觀察者模式

爲何有觀察者模式:

主要是鬆解耦(對應Kafka依賴broker發佈訂閱的這種徹底的解耦方式),對於對象間一對多的依賴關係,使得每當一個對象改變狀態,則全部依賴它的對象都會獲得通知並自動更新。 

優勢: 

  • 下降了目標與觀察者之間的耦合關係;

  • 目標與觀察者之間創建了一套觸發機制

缺點:

  • 目標與觀察者之間的依賴關係並無徹底解除,並且有可能出現循環引用。
  • 當觀察者對象不少時,通知的發佈會花費不少時間。

應用場景:

  • 對象間存在一對多關係,一個對象的狀態改變會影響其餘對象,而不知道具體有多少對象須要被改變;
  • 當一個抽象模型有兩個方面,其中一個方面依賴於另外一方面時,可將這兩者封裝在獨立的對象中以使它們能夠各自獨立地改變和複用

實現:

該模式具備如下角色

抽象主題(Subject):提供一個用於保存觀察者對象的彙集類和增長、刪除觀察者對象的方法,以及通知全部觀察者的抽象方法;
具體主題(Concrete Subject):在具體主題內部狀態改變時,給全部登記過的觀察者發出通知。
抽象觀察者(Observer):爲全部的具體觀察者定義一個接口,在獲得主題更改通知時更新本身。
具體觀察者(Concrete Observer):實現抽象觀察者中定義的抽象方法,以便在獲得目標的更改通知時更新自身的狀態。

讓耦合的雙方依賴於抽象,而不是依賴於具體,從而使各自的變化不會影響另外一邊的變化,這是依賴倒轉原則的最佳體現。

首先是一個觀察者接口

1 public interface Subscriber {
2     int receive(String publisher, String articleName);
3 }

有一個具體的觀察者(微信客戶端),實現receive方法

 1 public class WeChatClient implements Subscriber {
 2     private String username;
 3 
 4     public WeChatClient(String username) {
 5         this.username = username;
 6     }
 7 
 8     @Override
 9     public int receive(String publisher, String articleName) {
10         // 接收到推送時的操做
11         System.out.println(String.format("用戶<%s> 接收到 <%s>微信公衆號 的推送,文章標題爲 <%s>", username, publisher, articleName));
12         return 0;
13     }
14 }
View Code

接着是一個抽象主題,該類維護了一個訂閱者列表,實現了訂閱、取消訂閱、通知全部訂閱者等功能

 1 public class Publisher {
 2     private List<Subscriber> subscribers;
 3     private boolean pubStatus = false;
 4 
 5     public Publisher() {
 6         subscribers = new ArrayList<Subscriber>();
 7     }
 8 
 9     protected void subscribe(Subscriber subscriber) {
10         this.subscribers.add(subscriber);
11     }
12 
13     protected void unsubscribe(Subscriber subscriber) {
14         if (this.subscribers.contains(subscriber)) {
15             this.subscribers.remove(subscriber);
16         }
17     }
18 
19     protected void notifySubscribers(String publisher, String articleName) {
20         if (this.pubStatus == false) {
21             return;
22         }
23         for (Subscriber subscriber : this.subscribers) {
24             subscriber.receive(publisher, articleName);
25         }
26         this.clearPubStatus();
27     }
28 
29     protected void setPubStatus() {
30         this.pubStatus = true;
31     }
32 
33     protected void clearPubStatus() {
34         this.pubStatus = false;
35     }
36 }
View Code

最後是一個具體主題微信公衆號類,該類提供了 publishArticles 方法,用於發佈推送,當文章發佈完畢時調用父類的通知全部訂閱者方法

 1 public class WeChatAccounts extends Publisher {
 2     private String name;
 3 
 4     public WeChatAccounts(String name) {
 5         this.name = name;
 6     }
 7 
 8     public void publishArticles(String articleName, String content) {
 9         System.out.println(String.format("\n<%s>微信公衆號 發佈了一篇推送,文章名稱爲 <%s>,內容爲 <%s> ", this.name, articleName, content));
10         setPubStatus();
11         notifySubscribers(this.name, articleName);
12     }
13 }
View Code

客戶端使用

 1 public class Test {
 2     public static void main(String[] args) {
 3         WeChatAccounts accounts = new WeChatAccounts("小旋鋒");
 4 
 5         WeChatClient user1 = new WeChatClient("張三");
 6         WeChatClient user2 = new WeChatClient("李四");
 7         WeChatClient user3 = new WeChatClient("王五");
 8 
 9         accounts.subscribe(user1);
10         accounts.subscribe(user2);
11         accounts.subscribe(user3);
12 
13         accounts.publishArticles("設計模式 | 觀察者模式及典型應用", "觀察者模式的內容...");
14 
15         accounts.unsubscribe(user1);
16         accounts.publishArticles("設計模式 | 單例模式及典型應用", "單例模式的內容....");
17     }
18 }
View Code

輸出,當公衆號發佈一篇推送時,訂閱該公衆號的用戶可及時接收到推送的通知

1 <小旋鋒>微信公衆號 發佈了一篇推送,文章名稱爲 <設計模式 | 觀察者模式及典型應用>,內容爲 <觀察者模式的內容...> 
2 用戶<張三> 接收到 <小旋鋒>微信公衆號 的推送,文章標題爲 <設計模式 | 觀察者模式及典型應用>
3 用戶<李四> 接收到 <小旋鋒>微信公衆號 的推送,文章標題爲 <設計模式 | 觀察者模式及典型應用>
4 用戶<王五> 接收到 <小旋鋒>微信公衆號 的推送,文章標題爲 <設計模式 | 觀察者模式及典型應用>
5 
6 <小旋鋒>微信公衆號 發佈了一篇推送,文章名稱爲 <設計模式 | 單例模式及典型應用>,內容爲 <單例模式的內容....> 
7 用戶<李四> 接收到 <小旋鋒>微信公衆號 的推送,文章標題爲 <設計模式 | 單例模式及典型應用>
8 用戶<王五> 接收到 <小旋鋒>微信公衆號 的推送,文章標題爲 <設計模式 | 單例模式及典型應用>

7. 中介者模式

爲何有中介者模式:

把原來對象之間多對多的交互變成一對多的交互;減小各個對象之間的耦合,能夠更方便的增長減小對象。 

優勢: 

  • 簡化了對象之間的交互,將網狀結構轉換成相對簡單的星型結構,一對多關係更容易理解、維護和擴展;

  • 鬆耦合,能夠獨立的改變和複用每個同事和中介者,增長新的中介者和新的同事類都比較方便,更好地符合 「開閉原則

缺點:

  • 具體中介者類中包含了大量同事之間的交互細節,會致使具體中介者類很是複雜,系統很差維護

應用場景:

  • 一組定義良好的對象,如今要進行復雜的相互通訊;
  • 想經過一箇中間類來封裝多個類中的行爲,而又不想生成太多的子類。
  • 示例不少,好比
    • MVC模式中,Controller 是中介者,根據View 層的請求來操做Model 層

    • 好比組成原理中的主板,上邊插了CPU、內存、網卡、聲卡、硬盤各類東西

實現(參考https://blog.csdn.net/wwwdc1012/article/details/83389158):

該模式具備如下角色

抽象中介者(Mediator)角色:它是中介者的接口,提供了同事對象註冊與轉發同事對象信息的抽象方法。

具體中介者(ConcreteMediator)角色:實現中介者接口,定義一個 List 來管理同事對象,協調各個同事角色之間的交互關係,所以它依賴於同事角色。

抽象同事類(Colleague)角色:定義同事類的接口,保存中介者對象,提供同事對象交互的抽象方法,實現全部相互影響的同事類的公共功能

具體同事類(Concrete Colleague)角色:實現了在抽象同事類中聲明的抽象方法,每一個同事都知道中介者對象,要與同事通訊則把通訊告訴中介者。

好比,具體同事類,兩個本身寫的定時任務

 1 public class MyOneTask extends TimerTask {
 2     private static int num = 0;
 3     @Override
 4     public void run() {
 5         System.out.println("I'm MyOneTask " + ++num);
 6     }
 7 }
 8 
 9 public class MyTwoTask extends TimerTask {
10     private static int num = 1000;
11     @Override
12     public void run() {
13         System.out.println("I'm MyTwoTask " + num--);
14     }
15 }
View Code

客戶端

1 public class TimerTest {
2     public static void main(String[] args) {
3         // 注意:多線程並行處理定時任務時,Timer運行多個TimeTask時,只要其中之一沒有捕獲拋出的異常,
4         // 其它任務便會自動終止運行,使用ScheduledExecutorService則沒有這個問題
5         Timer timer = new Timer();
6         timer.schedule(new MyOneTask(), 3000, 1000); // 3秒後開始運行,循環週期爲 1秒
7         timer.schedule(new MyTwoTask(), 3000, 1000);
8     }
9 }
View Code

輸出

 1 I'm MyOneTask 1
 2 I'm MyTwoTask 1000
 3 I'm MyTwoTask 999
 4 I'm MyOneTask 2
 5 I'm MyOneTask 3
 6 I'm MyTwoTask 998
 7 I'm MyTwoTask 997
 8 I'm MyOneTask 4
 9 I'm MyOneTask 5
10 I'm MyTwoTask 996
11 I'm MyTwoTask 995
12 I'm MyOneTask 6
13 ...

具體中介者Timer

 1 public class Timer {
 2     private final TaskQueue queue = new TaskQueue();
 3     private final TimerThread thread = new TimerThread(queue);
 4     public void schedule(TimerTask task, long delay) {
 5         if (delay < 0)
 6             throw new IllegalArgumentException("Negative delay.");
 7         sched(task, System.currentTimeMillis()+delay, 0);
 8     }
 9     
10     public void schedule(TimerTask task, Date time) {
11         sched(task, time.getTime(), 0);
12     }
13     
14     private void sched(TimerTask task, long time, long period) {
15         if (time < 0)
16             throw new IllegalArgumentException("Illegal execution time.");
17 
18         if (Math.abs(period) > (Long.MAX_VALUE >> 1))
19             period >>= 1;
20 
21         // 獲取任務隊列的鎖(同一個線程屢次獲取這個鎖並不會被阻塞,不一樣線程獲取時纔可能被阻塞)
22         synchronized(queue) {
23             // 若是定時調度線程已經終止了,則拋出異常結束
24             if (!thread.newTasksMayBeScheduled)
25                 throw new IllegalStateException("Timer already cancelled.");
26 
27             // 再獲取定時任務對象的鎖(爲何還要再加這個鎖呢?想不清)
28             synchronized(task.lock) {
29                 // 判斷線程的狀態,防止多線程同時調度到一個任務時屢次被加入任務隊列
30                 if (task.state != TimerTask.VIRGIN)
31                     throw new IllegalStateException(
32                         "Task already scheduled or cancelled");
33                 // 初始化定時任務的下次執行時間
34                 task.nextExecutionTime = time;
35                 // 重複執行的間隔時間
36                 task.period = period;
37                 // 將定時任務的狀態由TimerTask.VIRGIN(一個定時任務的初始化狀態)設置爲TimerTask.SCHEDULED
38                 task.state = TimerTask.SCHEDULED;
39             }
40             
41             // 將任務加入任務隊列
42             queue.add(task);
43             // 若是當前加入的任務是須要第一個被執行的(也就是他的下一次執行時間離如今最近)
44             // 則喚醒等待queue的線程(對應到上面提到的queue.wait())
45             if (queue.getMin() == task)
46                 queue.notify();
47         }
48     }
49     
50     // cancel會等到全部定時任務執行完後馬上終止定時線程
51     public void cancel() {
52         synchronized(queue) {
53             thread.newTasksMayBeScheduled = false;
54             queue.clear();
55             queue.notify();  // In case queue was already empty.
56         }
57     }
58     // ...
59 }
View Code

Timer 中在 schedulexxx 方法中經過 TaskQueue 協調各類 TimerTask 定時任務,Timer 是中介者,TimerTask 是抽象同事類,而咱們本身寫的任務則是具體同事類

TimerThread 是 Timer 中定時調度線程類的定義,這個類會作爲一個線程一直運行來執行 Timer 中任務隊列中的任務。

Timer 這個中介者的功能就是定時調度咱們寫的各類任務,將任務添加到 TaskQueue 任務隊列中,給 TimerThread 執行,讓任務與執行線程解耦
注:

  1. 實現過程當中可能存在一些問題,好比若是中介者只有一個的話,並且預計中也沒有擴展的要求,那麼就能夠不定義Mediator接口,把中介者對象設爲單例
  2. 實現同事和中介者的通訊:一種實現方式是在Mediator接口中定義一個通用的方法,讓各個同事類來調用這個方法 ;另一種實現方式是能夠採用觀察者模式,把Mediator實現成爲觀察者,而各個同事類實現成爲Subject,這樣同事類發生了改變,會通知Mediator。
  3. 一般會去掉同事對象的父類,這樣可讓任意的對象,只要須要相互交互就能夠成爲同事;
  4. 中介者對象能夠不把同事做爲本身的屬性,能夠在處理方法中建立或者獲取須要的同事對象;
  5. 同事類須要知道中介者對象,在改變時通知它,但不必做爲屬性這麼強依賴,能夠把中介對象作成單例,直接在同事類的方法裏面去調用中介者對象。

8. 迭代器模式

爲何有迭代器模式:

將遍歷和實現分開,順序的訪問集合內部的對象,而又不暴露集合內部的表示。 

優勢: 

  • 遍歷由迭代器完成,簡化了集合類;
  • 支持以不一樣方式遍歷一個集合,能夠自定義迭代器的子類以支持新的遍歷;
  • 增長新的集合類和迭代器類都很方便,無須修改原有代碼;
  • 封裝性良好,爲遍歷不一樣的集合結構提供一個統一的接口。

缺點:

  • 增長類的個數,好比增長新的集合類須要增長新的具體迭代器

應用場景:

這個JDK已經提供了Iterator接口,咱們直接使用就行,如今不多本身寫迭代器了吧

實現

 該模式具備如下角色:

抽象迭代器(Iterator):抽象迭代器負責定義訪問和遍歷元素的接口,並且基本上是有一些固定的方法:

  • first()得到第一個元素
  • next()訪問下一個元素
  • hasNext()是否已經訪問到底部

具體迭代器(ConcreteIterator):具體迭代器角色要實現迭代器接口,完成容器元素的遍歷

抽象容器(Aggregate):容器角色負責提供建立具體迭代器角色的接口,必然提供一個相似createIterator()這樣的方法,在Java中通常是iterator()方法

具體容器(Concrete Aggregate):具體容器實現容器接口定義的方法,建立出容納迭代器的對象

好比,首先iterator,Iterator中最重要的兩個方法就是next方法和hasNext方法,構成了遍歷整個容器數據的兩個方法。remove方法若是沒有實現的話默認是會拋出一個不支持該操做的異常。

1 public interface Iterator<E> {
2     boolean hasNext();
3     E next();
4     default void remove() {
5         throw new UnsupportedOperationException();
6     }
7 }

而後ConcreteIterator

 1 private class Itr<E> implements Iterator<E> {
 2         private int position = -1;
 3         private Object[] data = elements;
 4         @Override
 5         public boolean hasNext() {
 6             return ++ position < data.length;
 7         }
 8 
 9         @Override
10         public E next() {
11             return (E) data[position];
12         }
13 }

而後是aggregate

1 public interface Iterable<E> {
2     Iterator<E> createIterator();
3 }

concrete aggregate

 1 public class MyContainer<E> implements Iterable<E>{
 2 
 3     Object[] elements;
 4     public MyContainer() {
 5         elements = new Byte[10];
 6         for (int i =0; i < 10; i ++)
 7             elements[i] = (byte) i;
 8     }
 9     @Override
10     public Iterator<E> createIterator() {
11         return new Itr();
12     }
13 
14 }

客戶端

1 iterator it = new MyContainer<Byte>().createIterator();
2 while (elements.hasNext()) {
3     System.out.println(elements.next());
4 }

注:爲何不讓容器直接繼承Iterator接口,還要整個Iterable接口?

1 爲了提供容器內數據安全性。假設容器實現了Iterator接口,那麼咱們全部經過Iterator接口進行的數據訪問修改操做都會直接影響容器內的數據,由於咱們訪問的數據和容器維護的數據是同一份數據。因此不如讓Iterator接口訪問數據副原本的安全。本身用本身的,互不影響。

9. 訪問者模式

爲何有訪問者模式:

將穩定的數據結構和易變的數據訪問操做分離,知足開閉原則。 

優勢: 

  • 將有關元素對象的訪問行爲集中到一個訪問者對象中,而不是分散在一個個的元素類中,符合單一職責原則
  • 能夠經過接受新的訪問者增長新的訪問操做十分方便,對擴展開放,符合開閉原則

缺點:

  • 訪問者模式中具體元素對訪問者公佈細節,破壞了對象的封裝性,違反了迪米特原則;
  • 每增長一個新的元素類,都要在每個具體訪問者類中增長相應的具體操做,違反了開閉原則;
  • 違反了依賴倒置原則,依賴了具體類,沒有依賴抽象

應用場景:

最複雜的設計模式,而且使用頻率不高。通常場景以下:

 一、對象結構中對象對應的元素類不多改變,但常常須要在此對象結構上定義新的操做;

 二、須要對一個對象結構中的元素進行不少不一樣的而且不相關的操做,而須要避免讓這些操做"污染"

實現:

抽象訪問者(Vistor):抽象訪問者爲對象結構中每個具體元素類聲明一個訪問操做,從這個操做的名稱或參數類型能夠清楚知道須要訪問的具體元素的類型,具體訪問者須要實現這些操做方法,定義對這些元素的訪問操做;
具體訪問者(ConcreteVisitor):具體訪問者實現了每一個由抽象訪問者聲明的操做;
抽象元素(Element):抽象元素通常是抽象類或者接口,它定義一個accept()方法,該方法一般以一個抽象訪問者做爲參數;
具體元素(ConcreteElement):具體元素實現了accept()方法,在accept()方法中調用訪問者的訪問方法以便完成對一個元素的操做;
對象結構(ObjectStructure):對象結構是一個元素的集合,它用於存放元素對象,而且提供了遍歷其內部元素的方法。

 它實際上是對不一樣數據結構的訪問封裝,例如

 1 // 抽象元素
 2 public interface Element {
 3     void accept(Visitor visitor);
 4 }
 5 // 具體元素A 
 6 public class ElementA implements Element {
 7     @Override
 8     public void accept(Visitor visitor) {
 9         visitor.visit(this);
10     }
11 }
12 // 具體元素B 
13 public class ElementB implements Element {
14     @Override
15     public void accept(Visitor visitor) {
16         visitor.visit(this);
17     }
18 }
19 // 抽象訪問者 
20 public abstract class Visitor {
21     public abstract void visit(ElementA elementA);
22     public abstract void visit(ElementB elementB);
23 }
24 // 具體訪問者A 
25 public class VisitorA extends Visitor {
26     @Override
27     public void visit(ElementA elementA) {
28         System.out.println(this + ":elementA");
29     }
30  
31     @Override
32     public void visit(ElementB elementB) {
33         System.out.println(this + ":elementB");
34     }
35 }
36 // 對象結構 
37 public class ObjectStructure {
38     private List<Element> elementList;
39  
40     public ObjectStructure() {
41         this.elementList = new ArrayList<>();
42     }
43  
44     public void addElement(Element element) {
45         elementList.add(element);
46     }
47  
48     public void removeElement(Element element) {
49         elementList.remove(element);
50     }
51  
52     public void accept(Visitor visitor) {
53         elementList.forEach(element -> element.accept(visitor));
54     }
55 }
56 // 客戶端 
57 @Test
58 public void VisitorTest() {
59    ObjectStructure objectStructure = new ObjectStructure();
60    objectStructure.addElement(new ElementA());
61    objectStructure.addElement(new ElementB());
62    objectStructure.accept(new VisitorA());
63 }
View Code

輸出:

com.test.visitor.visitorA@33a10788:ElementA
com.test.visitor.visitorA@33a10788:ElementB

10. 備忘錄模式

爲何有備忘錄模式:

保存對象的狀態,恢復對象的狀態 

優勢: 

  • 給用戶提供了一種能夠恢復狀態的機制,能夠是用戶可以方便的回到某個歷史的狀態;
  • 簡化了發起人類。發起人不須要管理和保存其內部狀態的各個備份,全部狀態信息都保存在備忘錄中,並由管理者進行管理,符合單一職責原則

缺點:

  • 資源消耗過大,若是須要保存的發起人類的成員變量太多,就不可避免須要佔用大量的存儲空間,每保存一次對象的狀態都須要消耗必定的系統資源

應用場景:

JDK、Spring不多用備忘錄的場景,通常經常使用於須要保存/恢復數據的相關狀態場景,好比:

瀏覽器回退:當咱們能夠在瀏覽器左上角點擊左箭頭回退到上一次的頁面,也能夠點擊右箭頭從新回到當前頁面

數據庫備份與還原:備份當前已有的數據或者記錄保留,還原已經保留的數據

編輯器撤銷與重作:寫錯時能夠按快捷鍵 Ctrl + z 撤銷,撤銷後能夠按 Ctrl + y 重作(md 居然如今才知道......)

虛擬機生成快照與恢復:虛擬機能夠生成一個快照,當虛擬機發生錯誤時能夠恢復到快照的樣子

Git版本管理:Git是最多見的版本管理軟件,每提交一個新版本,實際上Git就會把它們自動串成一條時間線,每一個版本都有一個版本號,使用 git reset --hard 版本號 便可回到指定的版本

棋牌遊戲悔棋:在棋牌遊戲中能夠悔棋,回退到上一步狀態

實現:

該模式具備如下角色:

發起人角色(Originator):當前時刻的內部狀態信息,提供建立備忘錄和恢復備忘錄數據的功能,實現其餘業務功能;

備忘錄角色(Memento):負責存儲發起人的內部狀態,在須要的時候提供這些內部狀態給發起人。

管理者角色(Caretaker):對備忘錄進行管理,提供保存與獲取備忘錄的功能,但其不能對備忘錄的內容進行訪問與修改。

還須要注意下寬接口和窄接口:

窄接口:只容許它把備忘錄對象傳給其餘的對象,針對的是負責人對象和其餘除發起人對象以外的任何對象。

寬接口:  容許它讀取全部的數據,以便根據這些數據恢復這個發起人對象的內部狀態,針對發起人。 

 1 // 備忘錄類
 2 public class Memento {
 3    private String state;
 4  
 5    public Memento(String state){
 6       this.state = state;
 7    }
 8  
 9    public String getState(){
10       return state;
11    }  
12 }
View Code
 1 // 發起人類
 2 public class Originator {
 3    private String state;
 4  
 5    public void setState(String state){
 6       this.state = state;
 7    }
 8  
 9    public String getState(){
10       return state;
11    }
12  
13    public Memento saveStateToMemento(){
14       return new Memento(state);
15    }
16  
17    public void getStateFromMemento(Memento Memento){
18       state = Memento.getState();
19    }
20 }
View Code
 1 // 管理者類
 2 import java.util.ArrayList;
 3 import java.util.List;
 4  
 5 public class CareTaker {
 6    private List<Memento> mementoList = new ArrayList<Memento>();
 7  
 8    public void add(Memento state){
 9       mementoList.add(state);
10    }
11  
12    public Memento get(int index){
13       return mementoList.get(index);
14    }
15 }
View Code
 1 // 客戶端
 2 public class MementoPatternDemo {
 3    public static void main(String[] args) {
 4       Originator originator = new Originator();
 5       CareTaker careTaker = new CareTaker();
 6       originator.setState("State #1");
 7       originator.setState("State #2");
 8       careTaker.add(originator.saveStateToMemento());
 9       originator.setState("State #3");
10       careTaker.add(originator.saveStateToMemento());
11       originator.setState("State #4");
12  
13       System.out.println("Current State: " + originator.getState());    
14       originator.getStateFromMemento(careTaker.get(0));
15       System.out.println("First saved State: " + originator.getState());
16       originator.getStateFromMemento(careTaker.get(1));
17       System.out.println("Second saved State: " + originator.getState());
18    }
19 }
View Code
// 輸出
Current State: State #4 First saved State: State #2 Second saved State: State #3

11. 解釋器模式

爲何有解釋器模式:

對於一些固定文法構建一個解釋句子的解釋器,其中文法是文法是用於描述語言語法結構的形式規則

優勢: 

  • 擴展性,修改語法規則只要修改相應的非終結符表達式就能夠了;若擴展語法,則只要增長非終結符類就能夠了

缺點:

  • 執行效率較低。解釋器模式中一般使用大量的循環和遞歸調用,當要解釋的句子較複雜時,其運行速度很慢,且代碼的調試過程也比較麻煩;
  • 會引發類膨脹。解釋器模式中的每條規則至少須要定義一個類,當包含的文法規則不少時,類的個數將急劇增長,致使系統難以管理與維護;
  • 可應用的場景比較少。在軟件開發中,須要定義語言文法的應用實例很是少,因此這種模式不多被使用到。

應用場景:

解釋器模式在實際的軟件開發中使用比較少,由於它會引發效率、性能以及維護等問題。若是碰到對錶達式的解釋,在JAVA 中能夠用 Expression4J 或 Jep 等來設計。該模式主要應用在:

  • 當語言的文法較爲簡單,且執行效率不是關鍵問題時;
  • 當問題重複出現,且能夠用一種簡單的語言來進行表達時;
  • 當一個語言須要解釋執行,而且語言中的句子能夠表示爲一個抽象語法樹的時候,如XML文檔解釋

實現:

該模式具備如下角色:

抽象解釋器(AbstractExpression):它包含了解釋方法 interpret;
終結符表達式(TerminalExpression):實現與文法中的元素相關聯的解釋操做,一般一個解釋器模式中只有一個終結表達式,但對應不一樣的終結符,好比四則運算中的終結符就是運算元素;
非終結符表達式(NonterminalExpression):文法中的每條規則對應於一個非終結表達式,好比四則運算中的加法運算、減法運算等
上下文(Context): 上下文環境類,包含解釋器以外的全局信息,通常是 HashMap

實現一個簡單的計算器 

 1 // 解釋器接口
 2 public interface Expression {
 3     int interpreter(Context context);//必定會有解釋方法
 4 }
 5 
 6 // 終結符表達式(在這個例子,用來存放數字,或者表明數字的字符)
 7 public class TerminalExpression implements Expression{
 8 
 9     String variable;
10     public TerminalExpression(String variable){
11 
12         this.variable = variable;
13     }
14     @Override
15     public int interpreter(Context context) {
16         return context.lookup(this);
17     }
18 }
19 
20 // 抽象非終結符表達式
21 public abstract class NonTerminalExpression implements Expression{
22     Expression e1,e2;
23     public NonTerminalExpression(Expression e1, Expression e2){
24 
25         this.e1 = e1;
26         this.e2 = e2;
27     }
28 }
29 
30 // 加法運算
31 public class PlusOperation extends NonTerminalExpression {
32 
33     public PlusOperation(Expression e1, Expression e2) {
34         super(e1, e2);
35     }
36 
37     //將兩個表達式相加
38     @Override
39     public int interpreter(Context context) {
40         return this.e1.interpreter(context) + this.e2.interpreter(context);
41     }
42 }
43 
44 // 減法表達式實現類
45 public class MinusOperation extends NonTerminalExpression {
46 
47     public MinusOperation(Expression e1, Expression e2) {
48         super(e1, e2);
49     }
50 
51  //將兩個表達式相減
52     @Override
53     public int interpreter(Context context) {
54         return this.e1.interpreter(context) - this.e2.interpreter(context);
55     }
56 }
57 
58 
59 // 上下文類(這裏主要用來將變量解析成數字【固然一開始要先定義】
60 public class Context {
61     private Map<Expression, Integer> map = new HashMap<>();
62 
63     //定義變量
64     public void add(Expression s, Integer value){
65         map.put(s, value);
66     }
67     //將變量轉換成數字
68     public int lookup(Expression s){
69         return map.get(s);
70     }
71 }
72 
73 // 測試類
74 public class Test {
75     public static void main(String[] args) {
76 
77         Context context = new Context();
78         TerminalExpression a = new TerminalExpression("a");
79         TerminalExpression b = new TerminalExpression("b");
80         TerminalExpression c = new TerminalExpression("c");
81         context.add(a, 4);
82         context.add(b, 8);
83         context.add(c, 2);
84 
85         System.out.println(new MinusOperation(new PlusOperation(a,b), c).interpreter(context));
86     }
87 }
88 
89 運行結果以下
90 -----------------------------------
91 10
View Code
相關文章
相關標籤/搜索