設計模式——行為型模式(責任鏈,命令,解釋器,迭代器,中介者,備忘錄,觀察者,狀態,空對象,策略,模板,訪問者)

  • 2019 年 10 月 23 日
  • 筆記

行為型模式是對在不同的對象之間劃分責任和演算法的抽象化。它不僅關注類和對象的結構,而且重點關注它們之間的相互作用。通過行為型模式,我們可以更加清晰地劃分類與對象的職責,並研究系統在運行時實例對象之間的交互

在系統運行時,對象並不是孤立的,它們可以通過相互通訊與協作完成某些複雜功能,一個對象在運行時也將影響到其他對象的運行

行為型模式分為兩種:

  1. 類行為型模式:類行為型模式使用繼承關係在幾個類之間分配行為。該模式主要通過多態等方式來分配父類與子類的職責
  2. 對象行為型模式:對象行為型模式使用對象的聚合關聯關係分配行為。該模式主要通過對象關聯等方式來分配兩個或多個類的職責

根據「合成復用原則」,系統中要盡量使用關聯關係來取代繼承關係,因此大部分行為型設計模式都屬於對象行為型設計模式

一、職責鏈模式

1、楔子

說一個病人看牙的時候,醫生不小心把拔掉的牙掉進了病人的嗓子里。各個科室的大夫都推卸責任,搞得病人樓上樓下跑了很多冤枉路,最後也沒解決問題

職責鏈模式就是一種「推卸」責任的模式,你在我這能解決問題,我就給你解決,不行就把你推給其它對象。至於誰最終解決了這個問題,管他呢!

2、解析

職責鏈模式:使多個對象都有機會處理請求,從而避免請求的發送者和接收者之間的耦合關係。將這些對象連成一條鏈並沿著這條鏈傳遞該請求,直到有一個對象處理它為止

模式組成:

  1. 抽象處理者角色:定義處理請求的介面。對於鏈的不同實現,可以在該角色中實現後繼鏈
  2. 具體處理者角色:實現抽象角色中定義的介面並處理它負責的請求,如果不能處理則訪問它的後繼者

職責鏈模式適用場景:

  • 有多個對象可以處理請求,具體誰處理運行時自動確定
  • 在不明確指定接收者的情況下,向多個對象中的一個提交一個請求
  • 可處理一個請求的對象集合被動態指定

3、舉例

有些項目很多地方需要使用代號,比如員工工號、檔案代號等,要求客戶使用時輸入。這些代號對特定的企業或者類別往往有一定的規則。因此可以讓用戶在系統參數中維護一定的規則,通過「代號自動生成器」為用戶生成代號

這裡只說明職責鏈模式的結構和使用,不提現功能細節

//抽象處理者角色  public interface CodeAutoParse {      //統一的處理請求使用的介面      String[] generateCode(String moduleCode, int number, String rule,String[] target)              throws BaseException;  }    //具體處理者角色。處理日期  public class DateAutoParse implements CodeAutoParse {      //獲取當前時間      private final Calendar currentDate = Calendar.getInstance();      //注入下一個處理者,系統中採用 Spring Bean管理      private CodeAutoParse theNextParseOfDate;        public void setTheNextParseOfDate(CodeAutoParse theNextParseOfDate){          this.theNextParseOfDate = theNextParseOfDate ;      }        /*       * 實現處理請求的介面       * 該介面首先判斷用戶定義的格式是否有流水號,有則解析,沒有則跳過       * 傳到下一個處理者       */      public String[] generateCode(String moduleCode, int number, String rule, String[] target) throws BaseException {          ... //這裡省略處理的業務            if(theNextParseOfDate != null)              return theNextParseOfDate.generateCode(moduleCode , number , rule , target);          else              return target;      }  }

二、命令模式

1、楔子

在設計介面時,大家可能注意到這種情況,同樣的菜單控制項,在不同的應用環境中功能完全不同;而菜單選項的某個功能可能和滑鼠右鍵的某個功能完全一致。按照最原始的設計,這些不同功能的菜單或者右鍵彈出菜單是要分開實現的,想像一下 word文檔上面一排的菜單要實現多少形似神非的菜單類?完全沒法搞啊

這是可以運用分離變化與不變的因素,將菜單觸發的功能分離出來,製作菜單時只提供一個統一的觸發介面。修改設計後,功能點可以被不同的菜單或者右鍵重用;且菜單控制項也能去除變化因素,提高了重用;同時分離了顯示邏輯和業務邏輯的耦合

這就是命令模式的雛形

2、解析

命令模式:將一個請求封裝為一個對象,從而可以用不同的請求對客戶進行參數化;對請求排隊或記錄請求日誌,以及支援可撤銷的操作

模式組成:

  1. 命令角色:聲明執行操作的介面。由 Java介面或抽象類實現
  2. 具體命令角色:將一個接收者對象綁定於一個動作;調用接收者相應方法以實現命令角色聲明的執行操作的介面
  3. 客戶角色:創建一個具體命令對象
  4. 請求者角色:調用命令對象執行該請求
  5. 接收者角色:知道如何實施與執行一個請求相關的操作。任何類都可能作為一個接收者

命令模式的本質是對命令進行封裝,將發出命令的責任和執行命令的責任分割開。每一個命令都是一個操作:請求的一方發出請求,要求執行一個操作;接收的一方收到請求,並執行操作

命令模式允許請求的一方和接收的一方獨立開來,使得請求的一方不必知道接收請求的一方的介面,更不必知道請求是怎麼被接收,以及操作是否被執行、何時被執行,以及是怎麼被執行的

命令模式使請求本身成為一個對象,這個對象和其他對象一樣可以被存儲和傳遞。該模式的關鍵在於引入了抽象命令介面,且發送者針對抽象命令介面編程,只有實現了抽象命令介面的具體命令才能與接收者相關聯

命令模式適用場景:

  1. 需要抽象出待執行的動作,然後以參數形式提供出來
  2. 在不同的時刻指定、排列和執行請求
  3. 需要支援取消操作
  4. 支援修改日誌功能
  5. 需要支援事務操作

3、舉例

命令模式在 Web開發中最常見的應用是 Struts中 Action的使用

在 Struts中 Action控制類是整個框架的核心,它連接著頁面請求和後台業務邏輯處理。按照框架設計,每一個繼承自 Action的子類,都實現 execute方法——調用後台真正處理業務的對象來完成任務

class Action {      /*       * Action提供了兩個版本的介面,實現了默認的空實現       */      public ActionForward execute( ActionMapping mapping,                                    ActionForm form,                                    ServletRequest request,                                    ServletResponse response)              throws Exception {          try {              return execute(mapping, form, (HttpServletRequest) request,                      (HttpServletResponse) response);          } catch (ClassCastException e) {              return null;          }      }      public ActionForward execute( ActionMapping mapping,                                    ActionForm form,                                    HttpServletRequest request,                                    HttpServletResponse response)              throws Exception {          return null;      }  }    //請求者角色,調用命令角色執行操作  class RequestProcessor {  ……      protected ActionForward processActionPerform(HttpServletRequest request,                                                   HttpServletResponse response,                                                   Action action,                                                   ActionForm form,                                                   ActionMapping mapping)              throws IOException, ServletException {          try {              return (action.execute(mapping, form, request, response));          } catch (Exception e) {              return (processException(request, response,e, form, mapping));          }      }  }

Struts框架為我們提供了以上兩個角色,要使用 Struts框架完成自己的業務邏輯,剩下的三個角色就要由我們自己實現:

  1. 先實現一個 Action的子類,並重寫 execute方法。在次方法中調用業務模組的相應對象完成任務
  2. 實現處理業務的業務類,充當接收者角色
  3. 配置 struts-config.xml配置文件,將自己的 Action和 Form以及相應頁面結合起來
  4. 編寫 jsp,在頁面中顯示的制定對應的處理 Action

在頁面提交請求後,Struts框架會根據配置文件中的定義,將 Action對象作為參數傳遞給 RequestProcessor類中的 processActionPerform()方法,由此方法調用 Action對象中的執行方法,進而調用業務層中的接收角色。這樣就完成了請求的處理

三、解釋器模式

1、解析

解釋器模式:定義語言的文法,並建立一個解釋器來解釋該語言中的句子

模式組成:

  1. 抽象表達角色:聲明一個抽象的解釋操作,該介面是所有具體表達式角色都要實現的
  2. 終結符表達式角色:具體表達式
    • 實現與文法中的終結符相關聯的解釋操作
    • 句子中的每個終結符需要該類的一個實例與之相呼應
  3. 非終結符表達式角色:具體表達式
    • 文法中的每條規則 R ::= R1R2...Rn 都需要一個非終結符表達式角色
    • 從 R1到 Rn的每個符號都維護一個抽象表達角色的實例變數
    • 實現解釋操作。解釋一般要遞歸的調用表示從 R1到 Rn的那些對象的解釋操作
  4. 上下文角色:包含解釋器外的一些全局資訊
  5. 客戶角色
    • 構建表示該文法定義的語言中的一個特定的句子的抽象文法樹
    • 調用解釋操作

解釋器模式描述了如何為簡單的語言定義一個文法,如何在該語言中表示一個句子,以及如何解釋這些句子

抽象語法樹:除了使用文法規則來定義一個語言,在解釋器模式中還可以通過一種稱之為抽 象語法樹的圖形方式來直觀地表示語言的構成,每一棵抽象語法樹對應一個語言實例

抽象語法樹描述了如何構成一個複雜的句子,通過對抽象語法樹的分析,可以識別出語言中的終結符和非終結符類

在解釋器模式中,每一種終結符和非終結符都有一個具體類與之對應,正因為使用類來表示每一個語法規則,使得系統具有較好的擴展性和靈活性

解釋器模式適用場景:

如果一種特定類型的問題發生的頻率足夠高,那麼可能就值得將該問題各個實例表述為一個簡單語言中的句子。這樣就可以構建一個解釋器,該解釋其通過解釋這些句子來解決問題。而且當文法簡單、效率不是關鍵的時候效果最好

2、舉例

舉一個加減乘除的例子

//上下文角色。使用 HashMap存儲變數對應的數據  class Context{      private Map valueMap = new HashMap();        public void addValue(Variable x , int y){          Integer yi = new Integer(y);          valueMap.put(x , yi);      }      public int LookupValue(Variable x){          int i = ((Integer)valueMap.get(x)).intValue();          return i ;      }  }    //抽象表達式角色。也可以用介面實現  abstract class Expression{      public abstract int interpret(Context con);  }    //終結符表達式角色  class Constant extends Expression{      private int i ;        public Constant(int i){          this.i = i;      }      public int interpret(Context con){          return i ;      }  }  class Variable extends Expression{      public int interpret(Context con) {   // this為調用 interpret方法的 Variable對象          return con.LookupValue(this);      }  }    //終結符表達式角色  class Add extends Expression{      private Expression left ,right ;        public Add(Expression left , Expression right) {          this.left = left ;          this.right = right ;      }      public int interpret(Context con){          return left.interpret(con) + right.interpret(con);      }  }  class Subtract extends Expression{      private Expression left , right ;        public Subtract(Expression left , Expression right) {          this.left = left ;          this.right = right ;      }      public int interpret(Context con){          return left.interpret(con) - right.interpret(con);      }  }  class Multiply extends Expression{      private Expression left , right ;        public Multiply(Expression left , Expression right){          this.left = left ;          this.right = right ;      }      public int interpret(Context con){          return left.interpret(con) * right.interpret(con);      }  }  class Division extends Expression{      private Expression left , right ;        public Division(Expression left , Expression right){          this.left = left ;          this.right = right ;      }      public int interpret(Context con){          try {              return left.interpret(con) / right.interpret(con);          } catch(ArithmeticException ae) {              System.out.println("㹿䰸᭄Ў 0ʽ");              return -11111;          }      }  }    //測試程式,計算 (a*b)/(a-b +2)  public class Test{      private static Expression ex ;      private static Context con ;        public static void main(String[] args){          con = new Context();          //設置變數、常量          Variable a = new Variable();          Variable b = new Variable();          Constant c = new Constant(2);          //為變數賦值          con.addValue(a , 5);          con.addValue(b , 7);          //運算。句子的結構由我們分析、構造          ex = new Division(new Multiply(a , b), new Add(new Subtract(a , b) , c));          System.out.println("䖤ㅫ㒧ᵰЎ˖" + ex.interpret(con));      }  }

四、迭代器模式

1、楔子

Java中常用 JDK提供的迭代介面進行 java collection的遍歷

Iterator it = list.iterator();  while(it.hasNext()) {      //使用 it.next(); 實現一些業務邏輯  }

2、解析

迭代器模式:提供一種方法訪問一個容器對象的各個元素,又不需暴露該對象的內部細節

模式組成:

  1. 迭代器角色:定義訪問和遍曆元素的介面
  2. 具體迭代器角色:實現迭代器介面,記錄遍歷中的當前位置
  3. 容器角色:提供創建具體迭代器角色的介面
  4. 具體容器角色:實現創建具體迭代器角色的介面,具體迭代器角色與該容器的結構相關

將遍歷對象中數據的行為提取出來封裝到一個迭代器中,通過專門的迭代器來遍歷對象的內部數據,這就是迭代器模式的本質。迭代器模式是「單一職責原則」的完美體現

迭代器模式適用場景:

  • 訪問一個容器對象的內容而無需暴露的它的內部表示
  • 支援對容器對象的多種遍歷
  • 為遍歷不同的容器結構提供一個統一的介面

3、舉例

先列舉迭代器模式的實現方式

  1. 迭代器角色定義了遍歷介面,但沒有規定由誰控制迭代。客戶程式控制遍歷進程是外部迭代器,迭代器自身控制迭代是內部迭代器
  2. 在迭代器模式中沒有規定誰實現遍歷演算法,好像理所當然的在迭代器角色中實現,但這樣就破壞了容器的封裝

Java collection中,具體迭代器角色定義在容器角色的內部類,這樣就保護了容器的封裝,同時容器提供了遍歷演算法介面,可以擴展自己的迭代器

//迭代器角色。定義遍歷介面  interface Iterator {      boolean hasNext();      Object next();      void remove();  }    //容器角色。以 List為例,它僅僅是一個介面,這裡略    //具體容器角色。實現了 List介面的 ArrayList等類,為了突出重點,這裡只羅列和迭代器相關的內容  //它以內部類的形式顯示,AbstractList是為了將各個具體容器角色的公共部分提取出來而存在  public abstract class AbstractList extends AbstractCollection implements List {      //負責創建具體迭代器角色的工廠方法      public Iterator iterator() {          return new Itr();      }        //作為內部類的具體迭代器角色      private class Itr implements Iterator {          int cursor = 0;          int lastRet = -1;          int expectedModCount = modCount;            public boolean hasNext() {              return cursor != size();          }            public Object next() {              checkForComodification();              try {                  Object next = get(cursor);                  lastRet = cursor + +;                  return next;              } catch (IndexOutOfBoundsException e) {                  checkForComodification();                  throw new NoSuchElementException();              }          }            public void remove() {              if (lastRet == -1)                  throw new IllegalStateException();              checkForComodification();              try {                  AbstractList.this.remove(lastRet);                  if (lastRet < cursor)                      cursor--;                  lastRet = -1;                  expectedModCount = modCount;              } catch (IndexOutOfBoundsException e) {                  throw new ConcurrentModificationException();              }          }            final void checkForComodification() {              if (modCount != expectedModCount)                  throw new ConcurrentModificationException();          }      }  }

五、中介者模式

1、楔子

對於一個模組,可能由很多對象構成,而且這些對象之間可能存在相互的引用,為了減少對象兩兩之間複雜的引用關係,使之成為一個松耦合的系統,我們需要使用中介者模式

2、解析

中介者模式:用一個中介對象來封裝一系列的對象交互。中介者使各對象不需要顯式地相互引用,從而使其耦合鬆散,而且可以獨立地改變它們之間的交互

模式組成:

  1. 抽象中介者角色:定義統一的介面用於各同事角色之間的通訊
  2. 具體中介者角色:通過協調各同事角色來實現協作行為,為此它需要知道並引用各個同事角色
  3. 同事角色:每個同事角色都知道對應的具體中介者角色,且與其他同事角色通訊時一定要通過中介者角色

中介者模式可以使對象之間的關係數量急劇減少

中介者承擔兩方面的職責:

  • 中轉作用:各個同事對象不再需要顯式引用其他同事,當需要和其他同事進行通訊時,通過中介者即可。中轉作用屬於中介者在結構上的支援
  • 協調作用:中介者可以更進一步的對同事之間的關係進行封裝,同事可以一致地和中介者進行交互,而不需要指明中介者需要具體怎麼做。中介者根據封裝在自身內部的協調邏輯,對同事的請求進行進一步處理,將同事成員之間的關係行為進行分離和封裝。協調作用屬於中介者在行為上的支援

中介者模式適用場景:

  • 系統中對象之間存在複雜的引用關係,產生的相互依賴關係結構混亂且難以理解
  • 一個對象由於引用了很多其他對象並且直接和這些對象通訊,導致難以復用該對象
  • 想通過一個中間類來封裝多個類中的行為,而又不想生成太多的子類

3、舉例

某公司欲開發一套圖形介面類庫。該類庫需要包含若干預定義的窗格(Pane)對象,例如 TextPane、ListPane、GraphicPane等,窗格之間不允許直接引用。基於該類庫的應用由一個包含一組窗格的窗口(Window)組成,窗口需要協調窗格之間的行為

//抽象中介者  abstract Mediator {      public abstract void mediator(Pane c);  }    //具體中介者  class Windows extends Mediator {      public TextPane textPane;      public ListPane listPane;      public GraphicPane graphicPane;        public void mediator(Pane c) {          if (c==textPane){              System.out.println("用戶點擊了TextPane,--ListPane,GraphicPane--進行顯示");              listPane.diaplay();              graphicPane.diaplay();          }else if (c==listPane){              System.out.println("用戶點擊了ListPane,--TextPane,GraphicPane--進行顯示");              textPane.diaplay();              graphicPane.diaplay();          }else {              System.out.println("用戶點擊了GraphicPane,--TextPane,ListPane--進行顯示");              textPane.diaplay();              listPane.diaplay();          }      }  }    //抽象組件類  abstract class Pane {      private Mediator mediator;        public void setMediator(Mediator mediator) {          this.mediator = mediator;      }      public void show(){          this.mediator.mediator(this);      }      public abstract void diaplay();  }    //具體組件類  class TextPane extends Pane{      public void diaplay() {          System.out.println("顯示TextPane");      }  }  class GraphicPane extends Pane {      public void diaplay() {          System.out.println("顯示GraphicPane");      }  }  class ListPane extends Pane{      public void diaplay() {          System.out.println("顯示ListPane");      }  }    //測試程式  public class MyClass {      public static void main(String[] args){          Windows windows=new Windows();            TextPane textPane=new TextPane();          ListPane listPane=new ListPane();          GraphicPane graphicPane=new GraphicPane();            textPane.setMediator(windows);          listPane.setMediator(windows);          graphicPane.setMediator(windows);            windows.listPane=listPane;          windows.textPane=textPane;          windows.graphicPane=graphicPane;            textPane.show();          System.out.println("——————————————");          graphicPane.show();      }  }

六、備忘錄模式

1、楔子

電影大話西遊給大家帶來了極佳的觀影體驗,其中的「月光寶盒」想必令很多人垂涎,如果世上真有如此寶物,或許真的會少一些傷感與後悔

好了,YY到此結束,現實世界中就不要想 peach了。但是程式中我們卻可以回到過去,備忘錄模式就是手中的「後悔葯」

2、解析

備忘錄模式:在不破壞封裝性的前提下捕獲一個對象的內部狀態,並在該對象之外保存這個狀態,以後就能將該對象恢復到原先保存的狀態

模式組成:

  1. 備忘錄角色:存儲備忘發起角色的內部狀態
  2. 備忘發起角色:創建一個備忘錄,記錄當前時刻它的內部狀態,在需要時使用備忘錄恢復內部狀態
  3. 備忘錄管理者角色:保存備忘錄,但不能操作或檢查備忘錄的內容

由於在備忘錄中存儲的是備忘發起角色的中間狀態,因此需要防止備忘發起角色以外的其他對象訪問備忘錄

備忘錄對象通常封裝了備忘發起角色的部分或所有的狀態資訊,而且這些狀態不能被其他對象訪問,也就是說不能在備忘錄對象之外保存備忘發起角色的狀態,因為暴露其內部狀態將違反封裝的原則,可能有損系統的可靠性和可擴展性

3、舉例

Java中保存備忘錄角色封裝的方法:

第一種是採用兩個不同介面限制訪問許可權。一個是提供比較完備操作狀態的寬介面,一個是作為標誌的窄介面。對於備忘發起角色採用寬介面訪問,其他角色或對象用窄介面訪問。該方法實現簡單,但需要人為規範約束

第二種是採用內部類控制訪問許可權,將備忘錄角色作為備忘發起角色的一個私有內部類。下面程式碼就使用該方法

class Originator{      //要保存的狀態      private int state = 90;      //ֱ保持一個備忘錄管理者角色的對象      private Caretaker c = new Caretaker();        //讀取備忘錄角色以恢復以前的狀態      public void setMemento(){          Memento memento = (Memento)c.getMemento();          state = memento.getState();          System.out.println("the state is " +state + " now");      }      //߯創建備忘錄角色,並存入當前狀態屬性,交給備忘錄管理者角色存放      public void createMemento(){          c.saveMemento(new Memento(state));      }        //this is other business methods...      //they maybe modify the attribute state        public void modifyState4Test(int m){          state = m;          System.out.println("the state is " +state + " now");      }      //作為私有內部類的備忘錄角色,它實現了窄介面,可以看到在第二種方法中寬介面已不再需要      //注意,裡面的屬性和方法都是私有的      private class Memento implements MementoIF {          private int state ;            private Memento(int state){              this.state = state ;          }          private int getState(){              return state;          }      }  }    //測試程式  public class TestInnerClass{      public static void main(String[] args){          Originator o = new Originator();          o.createMemento();          o.modifyState4Test(80);          o.setMemento();      }  }    //窄介面  interface MementoIF { }    //備忘錄管理者角色  class Caretaker{      private MementoIF m ;        public void saveMemento(MementoIF m){          this.m = m;      }      public MementoIF getMemento(){          return m;      }  }

七、觀察者模式

1、楔子

一個團伙在進行盜竊的時候,總有幾個人在門口放風,一旦有什麼風吹草動,立即通知裡面的同夥撤離。防風的人和裡面的人不一定認識,但並不影響通訊,他們已經提前商定好了暗號

放風者好偷竊者之間的關係就是觀察者模式的深刻體現

2、解析

觀察者模式:定義對象之間一對多的依賴關係,當一個對象的狀態發生改變時,所有依賴於他的對象都得到通知並被自動更新

模式組成:

  1. 抽象目標角色:目標角色知道它的觀察者,可以有任意多個觀察者觀察同一個目標,並且提供註冊和刪除觀察者對象的介面。Java中通常由抽象類或介面實現
  2. 抽象觀察者角色:為那些在目標發生改變時需要獲得通知的對象定義一個更新介面。Java中通常由抽象類或介面實現
  3. 具體目標角色:將有關狀態存入各個具體目標角色對象,當其狀態發生改變時,向它的各個觀察者發出通知
  4. 具體觀察者角色:存儲有關狀態,這些狀態應與目標的狀態保持一致。實現抽象目標角色的更新介面以使自身狀態與目標的狀態保持一致。在本角色內可以維護一個指向具體目標角色對象的引用

觀察者模式描述了如何建立對象與對象之間的依賴關係,如何構造滿足這種需求的系統

該模式的關鍵對象是觀察目標和觀察者,一個目標可以有任意數目的與之相依賴的觀察者,一旦目標的狀態發生改變,所有的觀察者都將得到通知

作為對這個通知的響應,每個觀察者都將即時更新自己的狀態,以與目標狀態同步,這種交互也稱為發布-訂閱。目標是通知的發布者,它發出通知時並不需要知道誰是它的觀察者,可以有任意數目的觀察者訂閱它並接收通知

3、舉例

JUnit為用戶提供了三種不同的測試結果顯示頁面,如何讓將測試的業務邏輯和顯示結果的介面很好的分離開就是觀察者模式的運用

//抽象觀察者角色。JUnit採用介面實現  //定義了4個不同的 update方法,對應4種不同的狀態變化  interface TestListener {      /**       * An error occurred.       */      public void addError(Test test, Throwable t);      /**       * A failure occurred.       */      public void addFailure(Test test, AssertionFailedError t);      /**       * A test ended.       */      public void endTest(Test test);      /**       * A test started.       */      public void startTest(Test test);  }    //具體觀察者角色。使用 TextUI說明  class ResultPrinter implements TestListener {      //省略無關程式碼        //實現介面 TestListener的4個方法      /**       * @see junit.framework.TestListener# addError(Test, Throwable)       */      public void addError(Test test, Throwable t) {          getWriter().print("E");      }      /**       * @see junit.framework.TestListener# addFailure(Test, AssertionFailedError)       */      public void addFailure(Test test, AssertionFailedError t) {          getWriter().print("F");      }      /**       * @see junit.framework.TestListener# endTest(Test)       */      public void endTest(Test test) {      }      /**       * @see junit.framework.TestListener#startTest(Test)       */      public void startTest(Test test) {          getWriter().print(".");          if (fColumn + + > = 40) {              getWriter().println();              fColumn = 0;          }      }  }    //目標角色。僅列出 Failures發生時如何通知觀察者  public class TestResult extends Object {      //存放測試 Failures的集合      protected Vector fFailures;      //存放註冊的觀察者的集合      protected Vector fListeners;        public TestResult() {          fFailures= new Vector();          fListeners= new Vector();      }      /**       * Adds a failure to the list of failures. The passed in exception       * caused the failure.       */      public synchronized void addFailure(Test test, AssertionFailedError t) {          fFailures.addElement(new TestFailure(test, t));          //通知各個觀察者的 addFailure方法          for (Enumeration e = cloneListeners().elements(); e.hasMoreElements(); ) {              ((TestListener)e.nextElement()).addFailure(test, t);          }      }      /**       * 註冊一個觀察者       */      public synchronized void addListener(TestListener listener) {          fListeners.addElement(listener);      }      /**       * 刪除一個觀察者       */      public synchronized void removeListener(TestListener listener) {          fListeners.removeElement(listener);      }      /**       * 返回一個觀察者集合的拷貝,防止對觀察者集合的非法操作       * 所有使用觀察者集合的地方都通過它       */      private synchronized Vector cloneListeners() {          return (Vector)fListeners.clone();      }      ...  }    //j建立各角色間的聯繫,JUnit中通過 TestRunner實現  class TestRunner extends BaseTestRunner {      private ResultPrinter fPrinter;        public TestResult doRun(Test suite, boolean wait) {          //在這裡註冊          result.addListener(fPrinter);          ...      }  }

八、狀態模式

1、解析

狀態模式允許一個對象在其內部狀態改變時改變它的行為

模式組成:

  1. 使用環境角色:客戶程式通過它滿足自己的需求。定義了客戶程式需要的介面,並且維護一個具體狀態角色的實例,這個實例決定當前的狀態
  2. 狀態角色:定義一個介面來封裝與使用環境角色某個特定狀態相關的行為
  3. 具體狀態角色:實現狀態角色定義的介面

狀態模式描述了對象狀態的變化以及對象如何在每一種狀態下表現出不同的行為。模式的關鍵是引入一個抽象類來專門表示對象的狀態,對象的每一種具體狀態類都繼承了該類,並在不同具體狀態類中實現了不同狀態的行為,包括各種狀態之間的轉換

優點:

  • 封裝了轉換規則
  • 枚舉可能的狀態,在枚舉狀態之前需要確定狀態種類
  • 將所有與某個狀態有關的行為放到一個類中,並且可以方便地增加新的狀態,只需要改變對象狀態即可改變對象的行為
  • 允許狀態轉換邏輯與狀態對象合成一體,而不是某一個巨大的條件語句塊
  • 可以讓多個環境對象共享一個狀態對象,從而減少系統中對象的個數

缺點:

  • 狀態模式的使用必然會增加系統類和對象的個數
  • 狀態模式的結構與實現都較為複雜,如果使用不當將導致程式結構和程式碼的混亂
  • 狀態模式對"開閉原則"的支援並不太好,對於可以切換狀態的狀態模式,增加新的狀態類需要修改那些負責狀態轉換的源程式碼,否則無法切換到新增狀態,而且修改某個狀態類的行為也需修改對應類的源程式碼

狀態模式適用場景:

  • 行為隨狀態改變而改變的場景
  • 條件、分支語句的代替者

2、舉例

創建一個 State介面和實現了 State介面的實體狀態類。Context是一個帶有某個狀態的類。然後使用 Context和狀態對象來演示 Context 在狀態改變時的行為變化

//創建一個介面  interface State {     public void doAction(Context context);  }    //創建實現介面的實體類  class StartState implements State {     public void doAction(Context context) {        System.out.println("Player is in start state");        context.setState(this);     }     public String toString(){        return "Start State";     }  }  class StopState implements State {     public void doAction(Context context) {        System.out.println("Player is in stop state");        context.setState(this);     }     public String toString(){        return "Stop State";     }  }    //創建 Context類  class Context {     private State state;       public Context(){        state = null;     }     public void setState(State state){        this.state = state;     }     public State getState(){        return state;     }  }    //使用 Context查看當狀態 State改變時的行為變化  public class StatePatternDemo {     public static void main(String[] args) {        Context context = new Context();          StartState startState = new StartState();        startState.doAction(context);          System.out.println(context.getState().toString());          StopState stopState = new StopState();        stopState.doAction(context);          System.out.println(context.getState().toString());     }  }

九、空對象模式

在空對象模式中,一個空對象取代 NULL 對象實例的檢查。Null 對象不是檢查空值,而是反應一個不做任何動作的關係。這樣的 Null 對象也可以在數據不可用的時候提供默認的行為

在空對象模式中,我們創建一個指定各種要執行的操作的抽象類和擴展該類的實體類,還創建一個未對該類做任何實現的空對象類,該空對象類將無縫地使用在需要檢查空值的地方

然後舉例說明:在一個圖書資訊查詢系統中調用一個方法,傳過去要查找圖書的ID,然後它返回要查找的圖書對象,這樣就可以調用對象的方法來輸出圖書的資訊

//提供構造函數和展示圖書資訊的show()方法  class ConcreteBook {      private int ID;      private String name;      private String author;        // 構造函數      public ConcreteBook(int ID, String name, String author) {          this.ID = ID;          this.name = name;          this.author = author;      }        public void show() {          System.out.println(ID + "**" + name + "**" + author);      }  }    //創建圖書對象的圖書工廠,主要提供一個獲得ConcreteBook的方法  class BookFactory {      public ConcreteBook getBook(int ID) {          ConcreteBook book = null;          switch (ID) {          case 1:              book = new ConcreteBook(ID, "設計模式", "GoF");              break;          case 2:              book = new ConcreteBook(ID, "被遺忘的設計模式", "Null Object Pattern");              break;          default:              book = null;// 其實這個可以省略,因為初始化已經賦值為null。              break;          }          return book;      }  }    //測試程式碼  public class Client {      static void main(String[] args) {          BookFactory bookFactory = new BookFactory();          ConcreteBook book = bookFactory.getBook(1);          book.show();      }  }

十、策略模式

1、楔子

完成一項任務,往往可以有多種不同的方式,每一種方式稱為一個策略,我們可以根據環境或者條件的不同選擇不同的策略來完成該項任務

2、解析

策略模式定義一系列的演算法,八折些演算法挨個封裝成擁有共同介面的單獨的類,並且使它們之間可以互換

模式組成:

  1. 演算法使用環境角色:演算法被引用到這裡和其他與環境有關的操作一起完成任務
  2. 抽象策略角色:規定所有具體策略角色需要的介面。Java中通常由抽象類或介面實現
  3. 具體策略角色:實現抽象策略角色定義的介面

策略模式是對演算法的封裝,它把演算法的責任和演算法本身分割開,委派給不同的對象管理。策略模式通常把一個系列的演算法封裝到一系列的策略類裡面,作為一個抽象策略類的子類

在策略模式中,應當由客戶端自己決定在什麼情況下使用什麼具體策略角色。策略模式僅僅封裝演算法,提供新演算法插入到已有系統以及老演算法從系統中「退休」的方便,策略模式並不決定在何時使用何種演算法,演算法的選擇由客戶端來決定

3、舉例

以局部管理器為例。在 java.awt類庫中有很多設定好的 Container對象的布局格式,這些格式可以在創建軟體介面時用到。如果不使用策略模式就沒有了對布局格式擴展的可能,因為修改 Container中的方法以讓它知道布局格式顯然不可行

Container中有關程式碼

LayoutManager layoutMgr; //對布局管理器介面的引用    //獲得在使用的具體布局管理器  public LayoutManager getLayout() {      return layoutMgr;  }  //設置要使用的具體布局管理器  public void setLayout(LayoutManager mgr) {      layoutMgr = mgr;      if (valid) {          invalidate();      }  }

可以看到 Container不關心使用的是什麼具體布局管理器,使得 Container不會隨著局部管理器的增多而修改本身,所以說策略模式是對變化的封裝

布局管理器介面程式碼

public interface LayoutManager {      void addLayoutComponent(String name, Component comp);      ...      Dimension minimumLayoutSize(Container parent);      void layoutContainer(Container parent);  }

具體的布局管理器就是對上面介面方法的實現和擴展,這裡不再顯示。下面是對它的使用

public class FlowLayoutWindow extends JApplet {      public void init() {          Containter cp = getContentPane();          cp.setLayout(new FlowLayout());          for(int i = 0 ; i < 20 ; i+ +)              cp.add(new JButton("Button" + i));      }      ...  }

十一、模板模式

1、解析

模板模式定義一個操作中的演算法的骨架,將一些步驟延遲到子類中。使得子類可以不改變一個演算法的結構即可重定義該演算法的某些特定步驟

模式組成:

  1. 抽象類:定義一到多個抽象方法,以供具體的子類實現它們,還要實現一個模板方法定義演算法的骨架。該模板方法不僅調用前面的抽象方法,也調用其他操作,只要能完成使命
  2. 具體類:實現父類中的抽象方法以完成演算法中與特定子類相關的步驟

使用模板方法可以使系統擴展性增強,最小化變化對系統的影響

2、舉例

// 步驟 1  // 創建一個抽象類,它的模板方法被設置為 final。  // Game.java  abstract class Game {     abstract void initialize();     abstract void startPlay();     abstract void endPlay();       //模板     public final void play(){          //初始化遊戲        initialize();          //開始遊戲        startPlay();          //結束遊戲        endPlay();     }  }    // 步驟 2  // 創建擴展了上述類的實體類。  // Cricket.java  class Cricket extends Game {     void endPlay() {        System.out.println("Cricket Game Finished!");     }     void initialize() {        System.out.println("Cricket Game Initialized! Start playing.");     }     void startPlay() {        System.out.println("Cricket Game Started. Enjoy the game!");     }  }    // Football.java  class Football extends Game {     void endPlay() {        System.out.println("Football Game Finished!");     }     void initialize() {        System.out.println("Football Game Initialized! Start playing.");     }     void startPlay() {        System.out.println("Football Game Started. Enjoy the game!");     }  }    // 步驟 3  // 使用 Game 的模板方法 play() 來演示遊戲的定義方式。  // TemplatePatternDemo.java  public class TemplatePatternDemo {     public static void main(String[] args) {        Game game = new Cricket();        game.play();        System.out.println();        game = new Football();        game.play();     }  }

十二、訪問者模式

1、楔子

對於系統中的某些對象,它們存儲在同一個集合中,且具有不同的類型。對於該集合中的對象,可以接受一類稱為訪問者的對象來訪問,而且不同的訪問者其訪問方式有所不同

在實際使用時,對同一集合對象的操作並不是唯一的,對相同的元素對象可能存在多種不同的操作方式。而且這些操作方式並不穩定,可能還需要增加新的操作,以滿足新的業務需求

此時訪問者模式就是一個值得考慮的解決方案

2、解析

訪問者模式表示一個作用於某對象結構中的各元素的操作,它使我們可以在不改變各元素的類的前提下定義作用於這些元素的新操作

模式組成:

  1. 訪問者角色:為對象結構中具體元素角色聲明一個訪問操作的介面。操作介面的名字和參數標識了發送訪問請求給具體訪問者的具體元素角色,這樣訪問者就可以通過該元素就是的特定介面直接訪問它
  2. 具體訪問者角色:實現每個訪問者角色聲明的操作
  3. 元素角色:定義 Accept操作,以訪問者為參數
  4. 具體元素角色:實現由元素角色提供的 Accept操作
  5. 對象結構角色:模式必備角色。具有如下特徵:能枚舉它的元素;可以提供一個高層的介面以允許該訪問者訪問它的元素;可以是一個複合(組合模式)或一個集合,如一個列表或一個無序集合

訪問者模式中對象結構存儲了不同類型的元素對象,以供不同訪問者訪問

訪問者模式包括兩個層次結構,一個是訪問者層次結構,提供了抽象訪問者和具體訪問者;一個是元素層次結構,提供了抽象元素和具體元素

相同的訪問者能以不同的方式訪問不同的元素,相同的元素可以接受不同訪問者以不同訪問方式訪問。在訪問者模式中,增加新的訪問者無須修改原有系統,系統具有較好的可擴展性

3、舉例

創建一個定義接收操作的 ComputerPart 介面。Keyboard、Mouse、Monitor 和 Computer 是實現了 ComputerPart 介面的實體類。再定義另一個介面 ComputerPartVisitor,它定義了訪問者類的操作。Computer 使用實體訪問者來執行相應的動作

演示類使用 Computer、ComputerPartVisitor 類來演示訪問者模式的用法

//定義一個表示元素的介面  interface ComputerPart {     public void accept(ComputerPartVisitor computerPartVisitor);  }    //創建擴展了上述類的實體類  class Keyboard  implements ComputerPart {     public void accept(ComputerPartVisitor computerPartVisitor) {        computerPartVisitor.visit(this);     }  }  class Monitor  implements ComputerPart {     public void accept(ComputerPartVisitor computerPartVisitor) {        computerPartVisitor.visit(this);     }  }  class Mouse  implements ComputerPart {     public void accept(ComputerPartVisitor computerPartVisitor) {        computerPartVisitor.visit(this);     }  }  class Computer implements ComputerPart {     ComputerPart[] parts;       public Computer(){        parts = new ComputerPart[] {new Mouse(), new Keyboard(), new Monitor()};     }     public void accept(ComputerPartVisitor computerPartVisitor) {        for (int i = 0; i < parts.length; i++) {           parts[i].accept(computerPartVisitor);        }        computerPartVisitor.visit(this);     }  }    //定義一個表示訪問者的介面  interface ComputerPartVisitor {     public void visit(Computer computer);     public void visit(Mouse mouse);     public void visit(Keyboard keyboard);     public void visit(Monitor monitor);  }    //創建實現了上述類的實體訪問者  class ComputerPartDisplayVisitor implements ComputerPartVisitor {     public void visit(Computer computer) {        System.out.println("Displaying Computer.");     }     public void visit(Mouse mouse) {        System.out.println("Displaying Mouse.");     }     public void visit(Keyboard keyboard) {        System.out.println("Displaying Keyboard.");     }     public void visit(Monitor monitor) {        System.out.println("Displaying Monitor.");     }  }    //使用 ComputerPartDisplayVisitor顯示 Computer的組成部分  public class VisitorPatternDemo {     public static void main(String[] args) {          ComputerPart computer = new Computer();        computer.accept(new ComputerPartDisplayVisitor());     }  }

Tags: