第25次文章:行為型模式

  • 2019 年 10 月 8 日
  • 筆記

本周以及上周的學習,主要將11種行為型模式全部結束。來個小總結哈!


行為型模式

關注系統中對象之間的相互交互,研究系統在運行時對象之間的相互通信和協作,進一步明確對象的職責,共有11種模式。

行為型模式匯總

(1)責任鏈模式

chain of responsibility

(2)命令模式

command

(3)解釋器模式

interpreter

(4)迭代器模式

iterator

(5)中介者模式

mediator

(6)備忘錄模式

memento

(7)觀察者模式

observer

(8)狀態模式

state

(9)策略模式

strategy

(10)模板方法模式

template method

(11)訪問者模式

visitor

一、責任鏈模式(chain of responsibility)

1、定義

將能夠處理同一類請求的對象連成一條鏈,所提交的請求沿着鏈傳遞,鏈上的對象逐個判斷是否有能力處理該請求,如果能,則處理,如果不能則傳遞給鏈上的下一個對象。

2、代碼實現

在日常生活中,我們經常面臨著請假等等問題。面對不一樣的請假時間,我們需要有不同的管理者來進行處理。比如請假3天以內,由主任處理;10天以內,由經理處理;30天以內,由總經理處理。由此便形成了一個責任鏈模式,當遇到僱員的請假請求時,從主任開始,層層向上傳遞,直到最後問題被解決。對於主任,經理和總經理,我們可以統一建立一個Leader抽象類,使得各位管理者統一繼承該抽象類,此抽象類中提供一個處理請求的方法。

(1)首先創建一個請假的請求模板

public class Leave {  private String empName;//請假的僱員的名字  private int leaveDays;//請假天數  private String response;//請假理由    public Leave(String empName, int leaveDays, String response) {    super();    this.empName = empName;    this.leaveDays = leaveDays;    this.response = response;  }    public String getEmpName() {    return empName;  }  public int getLeaveDays() {    return leaveDays;  }  public String getResponse() {    return response;  }}

(2)創建一個Leader抽象類,提供一個抽象處理請求的方法

public abstract class Leader {  private String name;  private Leader nextLeader;    public Leader(String name) {    super();    this.name = name;  }    public Leader getNextLeader() {    return nextLeader;  }  public void setNextLeader(Leader nextLeader) {    this.nextLeader = nextLeader;  }  public String getName() {    return name;  }  //處理請求的方法  public abstract void handleRequest(Leave leave);}

(3)依次創建相關的領導類,並實現相關的方法

//主任類public class Director extends Leader{  public Director(String name) {    super(name);  }  @Override  public void handleRequest(Leave leave) {    if(leave.getLeaveDays()<3) {      System.out.println("請假人:"+leave.getEmpName()+"t請假時間:"+leave.getLeaveDays()+          "天t請假原因:"+leave.getResponse());      System.out.println("審批人:主任"+this.getName()+"t審批意見:同意請假!");    }else {      if(this.getNextLeader()!=null) {        this.getNextLeader().handleRequest(leave);      }    }    }}
//經理類public class Manager extends Leader{  public Manager(String name) {    super(name);  }  @Override  public void handleRequest(Leave leave) {    if(leave.getLeaveDays()<10) {      System.out.println("請假人:"+leave.getEmpName()+"t請假時間:"+leave.getLeaveDays()+          "天t請假原因:"+leave.getResponse());      System.out.println("審批人:經理"+this.getName()+"t審批意見:同意請假!");    }else {      if(this.getNextLeader()!=null) {        this.getNextLeader().handleRequest(leave);      }    }    }}
//總經理類public class SuperiorManager extends Leader{  public SuperiorManager(String name) {    super(name);  }  @Override  public void handleRequest(Leave leave) {    if(leave.getLeaveDays()<30) {      System.out.println("請假人:"+leave.getEmpName()+"t請假時間:"+leave.getLeaveDays()+          "天t請假原因:"+leave.getResponse());      System.out.println("審批人:總經理"+this.getName()+"t審批意見:同意請假!");    }else {      System.out.println("此人想要辭職,竟然請假"+leave.getLeaveDays()+"天!");    }  }}

(4)測試代碼

public class Client {  public static void main(String[] args) {    Director a1 = new Director("張三");    Manager a2 = new Manager("李四");    SuperiorManager a3 = new SuperiorManager("王五");    //設置優先級    a1.setNextLeader(a2);    a2.setNextLeader(a3);    Leave leave = new Leave("趙六",46,"回家探親");    Leave leave1 = new Leave("趙六",1,"回家探親");    Leave leave2 = new Leave("趙六",5,"回家探親");    Leave leave3 = new Leave("趙六",25,"回家探親");    a1.handleRequest(leave);    a2.handleRequest(leave1);    a1.handleRequest(leave2);    a1.handleRequest(leave3);  }}

查看結果圖:

tips:

(1)在責任鏈模式中,我們首先需要自己創建每一個領導類的對象,按照我們的業務邏輯設置相應的優先級。

(2)在選擇第一個處理者時,如果當前處理者無法處理相應的請求,那麼將會直接將此請求傳遞給後面的管理者進行處理。但是也可以直接選擇最高級的管理者(SuperiorManager)作為第一個處理者,如紅色框中所示:我們對於請假5天的員工,也可以直接將其遞交給總經理進行處理。

3、開發中常見的場景

(1)java中,異常機制就是一種責任鏈模式。一個try可以對應多個catch,當第一個catch不匹配類型,則自動跳到第二catch。

(2)JavaScript語言中,事件的冒泡和捕獲機制。java語言中,事件的處理採用觀察者模式。

(3)servlet開發中,過濾器的鏈式處理。

(4)Struts2中,攔截器的調用也是典型的責任鏈模式。

4、總結

對於責任鏈模式,其實完全可以使用ifelse語句進行判斷處理,但是之所以還是推崇責任鏈模式,還是因為程序的擴展性問題。如果我們在後續的維護中,需要再增加一個處理10天——20天請求的副總經理,那麼在ifelse條件下,我們將需要在源代碼的基礎上更改ifelse邏輯。然而使用責任鏈模式的話,我們就只需要再增加一個副總經理類,然後在設置相關優先級的時候直接將副總經理設置為經理的nextleader就可以了,這樣就大大提高了整個程序後期的維護效率!

二、迭代器模式(iterator)

1、場景

(1)提供一種可以遍歷聚合對象的方式。又稱為:游標cursor模式

(2)聚合對象:存儲數據

(3)迭代器:遍曆數據

2、代碼實現

這裡並沒有具體的現實場景提供,迭代器在講解容器的時候,已經有所提到,這裡更注重的是我們自己來實現迭代器的底層原理。

(1)創建一個迭代器接口,規定幾種特定的方法。

public interface MyIterator {  void first();  //將游標指向第一個元素  void next();  //將游標指向下一個元素  boolean hasNext();  //判斷是否還有下一個元素  boolean isFirst();  //是否是第一個元素  boolean isLast();  //是否是最後一個元素  Object getCurrentObject();  //獲取當前游標指向的對象}

tips: 這裡主要是提供幾個常見的方法,需要後面的具體的迭代器加以實現其內部方法。

(2)自定義一個聚合類

public class ConcreteMyAggregate {  List<Object> list = new ArrayList<Object>();    public void add(Object object) {    this.list.add(object);  }  public void remove(Object object) {    this.list.remove(object);  }  public Object getObject(int index) {    return this.list.get(index);  }  public int getSize() {    return this.list.size();  }  public List<Object> getList() {    return list;  }  public void setList(List<Object> list) {    this.list = list;  }  //獲得迭代器  public ConcreteIterator creteIterator() {    return new ConcreteIterator();  }    //使用內部類定義迭代器,可以直接使用外部類的屬性  private class ConcreteIterator implements MyIterator{    private int cursor=0;    @Override    public void first() {      cursor =0;    }    @Override    public void next() {      if (cursor < list.size()) {        cursor++;      }    }    @Override    public boolean hasNext() {      return cursor == (list.size())?false:true ;    }      @Override    public boolean isFirst() {      return cursor==0;    }      @Override    public boolean isLast() {      return cursor==(list.size()-1);    }      @Override    public Object getCurrentObject() {      return list.get(cursor);    }    }}

tips:

1.在具體的自定義聚合類中,主要是對存儲聚合類的容器list進行增刪改操作;

2.與此同時,我們定義了一個迭代器內部類,實現迭代器MyIterator接口。將迭代器定義為內部類,主要是為了方便調用內部的list屬性,可以直接使用內部類屬性cursor作為索引,對外部類的屬性list進行操作。這樣極大的方便了迭代器在編寫過程。

(3)測試代碼

public class Client {  public static void main(String[] args) {    ConcreteMyAggregate cma = new ConcreteMyAggregate();    cma.add("aaa");    cma.add("bbb");    cma.add("ccc");    cma.add("sss");    System.out.println(cma.getSize());    System.out.println(cma.getObject(0));    MyIterator iterator = cma.creteIterator();    while(iterator.hasNext()) {      System.out.println(iterator.getCurrentObject());      iterator.next();    }  }}

結果圖:

tips:在整個迭代器模式中,我們最主要的是體會迭代器的底層實現思想。關注每一個游標指向不同索引時,代表的是容器中的哪一個位置。

三、中介者模式(mediator)

1、核心

(1)如果一個系統中對象之間的聯繫呈現為網狀結構,對象之間存在大量多對多關係,將導致關係及其複雜,這些對象稱為「同事對象」。

(2)我們可以引入一個中介者對象,使各個同事對象只跟中介者對象打交道,將複雜的網絡結構化解為星型結構。

2、中介者模式的本質

解耦多個同事對象之間的交互關係。每個對象都持有中介者對象的引用,只跟中介者對象打交道。我們通過中介者對象統一管理這些交互關係。

3、代碼實現

在一個公司中,總會有許多部門,部門之間一般不會直接交流,而是通過一個總經理(或其他身份的領導者)進行相關的溝通,然後總經理負責各個部門之間的協調發展。這裡我們以研發部,市場部以及財務部為例。

(1)部門接口和每個部門的實現

// 部門類,同事對象接口public interface Department {  void selfAction();  //部門自己的事情  void outAction();  //部門以外的事情}
// 研發部(同事對象)public class Development implements Department{  private Mediator m;//持有中介者的引用  public Development(Mediator m) {    super();    this.m = m;    m.register("研發部", this); //將同事對象註冊在其中  }  @Override  public void selfAction() {    System.out.println("專心科研,生產新產品!");  }  @Override  public void outAction() {    System.out.println("缺錢,請求財政部資金支持!");        m.command("財政部");  }}
//財政部(同事對象)public class Financial implements Department{  private Mediator m;//持有中介者的引用  public Financial(Mediator m) {    super();    this.m = m;    m.register("財政部", this); //將同事對象註冊在其中  }  @Override  public void selfAction() {    System.out.println("數錢!");  }  @Override  public void outAction() {    System.out.println("錢太多,花不完!");    m.command("財政部");  }}
// 市場部(同事對象)public class Market implements Department{  private Mediator m;//持有中介者的引用  public Market(Mediator m) {    super();    this.m = m;    m.register("市場部", this); //將同事對象註冊在其中  }  @Override  public void selfAction() {    System.out.println("跑市場,接業務!");  }  @Override  public void outAction() {    System.out.println("缺錢,請求財政部資金支持!");    m.command("財政部");  }}

tips:在每一個同事類對象中,分別向外提供一個展示自己的內部工作的方法,和一個向外部請求的方法。此處可以根據需要設計的很複雜,這裡僅僅是做一個簡單的假設,使用簡單的邏輯來說明中介者模式的內容。

(2)中介者的接口以及相關實現

public interface Mediator {  void register(String dname,Department department);  void command(String dname);}
// 總經理類(中介者對象)public class President implements Mediator{  Map<String,Department> map = new HashMap<String,Department>();//註冊所有的部門  @Override  public void register(String dname, Department department) {    map.put(dname, department);  }  /**   * 在總經理類的命令方法中,可以根據需求,傳入不同的參數,以實現複雜的功能   * 此處僅為簡單的介紹,不做深入   */  @Override  public void command(String dname) {    map.get(dname).selfAction();  }  }

tips:在中介者中,我們提供一個map進行存放所有的部門,也屬於對每個部門的註冊,

(3)測試代碼

public class Client {  public static void main(String[] args) {    President p = new President();    Market market = new Market(p);    Development development = new Development(p);    Financial financial = new Financial(p);    market.selfAction();    System.out.println("###################");    market.outAction();  }}

結果圖:

tips:

1.在測試代碼中,我們主要使用了「市場部「和「財政部「作為一個相互調用的關係進行測試。

2.在測試代碼中,我們創建了一個中介者對象p,在整個代碼的運行中,我們並沒有直接使用此中介對象,僅僅是作為一個參數屬性傳遞給了每一個同事對象。通過源碼我們也可以看出來,中介者主要是在各個同時對象的內部發揮作用。在每個同事對象的外部方法聲明中,我們還可以增加ifelse語句,來完成更加複雜的請求。這些就屬於中介者模式的優化!

4、開發中常見的場景

(1)MVC模式(其中的C,控制器就是一個中介者對象。M和V都和他打交道)

(2)窗口遊戲程序,窗口軟件開發中窗口對象也是一個中介者對象

(3)圖形界面開發GUI中,多個組件之間的交互,可以通過引入一個中介者對象來解決,可以是整體的窗口對象或者DOM對象。

四、命令模式(command)

1、介紹

將一個請求封裝為一個對象,從而使我們可用不同的請求對客戶進行參數化;對請求排隊或者記錄請求日誌,以及支持可撤銷的操作。也稱之為:動作Action模式、事務transaction模式

2、結構

(1)Command抽象命令類

(2)ConcreteCommand具體命令類

(3)Invoker調用者/請求者:請求的發送者,它通過命令對象來執行請求。一個調用者並不需要在設計時確定其接受者,因此它只與抽象命令類之間存在關聯。在程序運行時,將調用命令對象的execute(),間接調用接受者的相關操作。

(4)Receiver接受者

-接受者執行與請求相關的操作,具體實現對請求的業務處理。

-未抽象前,實際執行操作內容的對象。

(5)Client客戶類

在客戶類中需要創建調用者對象、具體命令類對象,在創建具體命令對象時指定對應的接受者。發送者和接收者之間沒有直接關係,都通過命令對象間接調用。

3、代碼實現

按照上面的結構,我們依次實現相應的類型

(1)創建Receiver,作為真正命令的執行者

public class Receiver {  public void action() {    System.out.println("receiver action!");  }}

(2)對命令類的抽象和具體實現

public interface Command {  /**   * 這個方法是一個返回值為空的方法。   * 實際項目中,可以根據需求設計多個不同的方法   */  void execute();}  class ConcreteCommand implements Command{  private Receiver receiver ;  public ConcreteCommand(Receiver receiver) {    super();    this.receiver = receiver;  }  @Override  public void execute() {    //在執行命令前或後可以執行多條命令    receiver.action();  }}

(3)命令的調用者和發起者

//調用者、發起者public class Invoke {  private Command command;  //也可以通過容器List<Command>容納很多命令,數據庫底層的事務管理就是類似的結構!  public Invoke(Command command) {    super();    this.command = command;  }  //業務方法,用於調用命令類的方法  public void call() {    command.execute();  }}

(4)測試代碼

public class Client {  public static void main(String[] args) {    Command command = new ConcreteCommand(new Receiver());    Invoke invoker = new Invoke(command);    invoker.call();    //以上方法與下列相同    new Receiver().action();  }}

結果圖:

tips:

1.在測試代碼中,我們寫了兩個調用receiver.action的方法。第一種是使用的命令模式,第二種是直接通過new對象的方式進行。

2.直觀來看,第二種方式更加簡潔。如果一個程序能夠一直保持不變,那麼我們很多設計模式都是沒有必要使用的。在使用設計模式的時候,大多數情況都是為整個程序的後期維護和擴展而考慮的。所以,將上述的兩種方法進行簡單的對比之後,我們可以發現,使用第一種命令模式的優勢在於,我們可以在receiver.action方法執行前後,添加許多自己的方法與功能,比如將此命令記入日誌,命令的撤銷和恢復等等。這些才屬於我們設計模式的優勢,而不是簡單的看一條命令的運行簡潔與否。

4、開發中常見的場景

(1)Struts2中,action的整個調用過程中就有命令模式

(2)數據庫事務機制的底層實現

(3)命令的撤銷和恢復

五、解釋器模式(Interpreter)

1,介紹

(1)是一種不常用的設計模式。

(2)用於描述如何構成一個簡單的語言解釋器,主要用於面向對象語言開發的編譯器和解釋器設計。

(3)當我們需要開發一種新的語言時,可以考慮使用解釋器模式。

(4)盡量不要使用解釋器模式,後期維護會有很大麻煩。在項目中,可以使用Jruby,Groovy,java的Js引擎來代替解釋器的作用,彌補java語言的不足。

2、開發中常見的場景

(1)EL表達式的處理

(2)正則表達式計時器

(3)SQL語法的解釋器

(4)數學表達式解析器

3、補充

解釋器模式的使用,更多的在於重新編譯一門新的語言,難以找到對應的場景進行實例化,所以此處不對其進行代碼實現。後期假如使用到此模式,可以再重新學習編寫。

六、訪問者模式(Visitor)

1、模式動機

對於存儲在一個集合中的對象,他們可能具有不同的類型(即使有一個公共的接口),對於該集合中的對象,可以接受一類稱為訪問者的對象來訪問,不同的訪問者其訪問方式也有所不同。

2、定義

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

3、開發中的場景(應用範圍非常窄,了解即可)

(1)XML文檔解析器設計

(2)編譯器的設計

(3)複雜對象的處理

七、策略模式(strategy)

1、場景

某個市場人員接到單後的報價策略(CRM系統中常見問題)。報價策略很複雜,可以簡單作如下分類:

(1)普通客戶小批量報價

(2)普通客戶大批量報價

(3)老客戶小批量報價

(4)老客戶大批量報價

假如我們不使用任何模式,直接使用ifelse語句來完成,也是可以達到基本的要求,那麼代碼將會是下面的邏輯:

public class TestStrategy {  public double getPrice(String type,double price) {    if(type.equals("普通客戶小批量")) {      System.out.println("不打折,原價出售");      return price;    }else if(type.equals("普通客戶大批量")) {      System.out.println("打九折");      return price*0.9;    }else if(type.equals("老客戶小批量")) {      System.out.println("打八五折");      return price*0.85;    }else if(type.equals("老客戶大批量")) {      System.out.println("打八折");      return price*0.8;    }    return price;  }}

tips:我們來對其進行簡單的分析。

  • 實現起來比較簡單,符合一般開發人員的思路。
  • 假如類型特別多,算法比較複雜時,整個條件語句的代碼就變得很長,難於維護。
  • 如果有新增類型,就需要頻繁的修改此處的代碼!
  • 不符合開閉原則。

所以針對上面的這些問題:具體選用哪個報價策略,這需要根據實際情況來確定。這時候,我們採用策略模式即可。

2、核心

策略模式對應於解決某一個問題的算法族,允許用戶從該算法族中任選一個算法解決某一問題,同時可以方便的更換算法或者增加新的算法。並且由客戶端決定調用哪個算法。

3、代碼實現

(1)構建一個策略接口,並實現相應的策略類

public interface Strategy {  public double getPrice(double standardPrice);}
public class NewCustomerFewStrategy implements Strategy{  @Override  public double getPrice(double standardPrice) {    System.out.println("不打折,原價出售");    return standardPrice;  }}
public class NewCustomerManyStrategy implements Strategy{  @Override  public double getPrice(double standardPrice) {    System.out.println("打九折");    return standardPrice*0.9;  }}
public class OldCustomerFewStrategy implements Strategy{  @Override  public double getPrice(double standardPrice) {    System.out.println("打八五折");    return standardPrice*0.85;  }}
public class OldCustomerManyStrategy implements Strategy{  @Override  public double getPrice(double standardPrice) {    System.out.println("打八折");    return standardPrice*0.8;  }}

(2)構建一個環境變量,用於客戶端和各個算法之間的交互

public class Context {  private Strategy strategy;  //當前採用的算法對象  //可以通過構造器來注入  public Context(Strategy strategy) {    super();    this.strategy = strategy;  }  //可以通過set方法來注入  public void setStrategy(Strategy strategy) {    this.strategy = strategy;  }  public void computePrice(double price) {    System.out.println("您的報價為:"+strategy.getPrice(price));  }}

tips:Context類主要負責和具體的策略類交互,這樣的話,具體的算法和直接的客戶端調用分離了,使得算法可以獨立於客戶端獨立的變化, 如果使用spring的依賴注入功能,還可以通過配置文件,動態的注入不同策略對象,動態的切換不同的算法。

(3)測試代碼

public class Client {  public static void main(String[] args) {    Context context = new Context(new OldCustomerFewStrategy());    context.computePrice(1000);    context.computePrice(800);    context.setStrategy(new OldCustomerManyStrategy());    context.computePrice(1000);    context.setStrategy(new NewCustomerManyStrategy());    context.computePrice(1000);    context.setStrategy(new NewCustomerFewStrategy());    context.computePrice(1000);  }}

結果圖:

tips :對比之前的ifelse語句,策略模式的整體代碼更加複雜,但是策略模式的優勢就在於整個代碼的擴展性強。假如在上述場景中,如果需要再增加一個新的策略,那麼我們可以直接增加相應的策略類即可,並不要更改任何源代碼。

4、本質

分離算法,選擇實現。

八、模板方法模式(template method)

1、介紹

模板方法模式是編程中經常用到的模式,它定義了一個操作中的算法骨架,將某些步驟延遲到子類中實現。這樣,新的子類可以在不改變一個算法結構的前提下重新定義該算法的某些特定步驟。

2、核心

處理某個流程的代碼已經都具備,但是其中某個節點的代碼暫時不能確定。因此,我們採用模板方法模式,將這個節點的代碼實現轉移給子類完成。即:處理步驟父類中定義好,具體實現延遲到子類中定義。

3、方法回調(鉤子方法)

子類不能調用父類,而通過父類調用子類。這些調用步驟已經在父類中寫好了,完全由父類控制整個過程。

4、代碼實現

比如在銀行辦理業務過程中,我們都知道具體的流程就是:排隊取號、進行交易、服務打分。整個流程是清晰的,但是具體到「交易」步驟的時候,對於每個顧客而言,這是不清楚的。所以我們需要對「交易」步驟進行具體的實現。

(1)創建一個方法流程模板

/** * 定義一個模板方法 */public abstract class BankTempleteMethod {  public void line() {    System.out.println("排隊取號!");  }  public abstract void transcet();  public void pp(){    System.out.println("對服務進行打分");  }  /**   * 此處屬於模板方法,整個架構固定不動   * 所以使用final進行修飾,不允許子類修改   */  public final void process() {    this.line();    this.transcet();    this.pp();  }}

tips:由於交易的類型不確定,但是又屬於一定需要實現的一個方法,所以我們將整個方法模板設置為抽象類,在子類中,具體實現這個交易的細節。

(2)具體實現

public class Client {  public static void main(String[] args) {    BankTempleteMethod btm = new BankTempleteMethod() {      @Override      public void transcet() {        System.out.println("存錢!!!");      }    };    btm.process();    BankTempleteMethod btm2 = new BankTempleteMethod() {      @Override      public void transcet() {        System.out.println("取錢!!!");      }    };    btm2.process();  }}

結果圖:

tips:在我們具體實現抽象方法的時候,由於每個具體對象的抽象方法一般都是不同的,所以我們一般都是使用匿名內部類的方法完成抽象方法的具體實現。

5、用到模板方法模式的場景

實現一個算法時,整體步驟很固定。但是,某些部分易變。易變部分可以抽象出來,供子類實現。

九、狀態模式(state)

1、核心

用於解決系統中複雜對象的狀態轉換以及不同狀態下行為的封裝問題。

2、結構

(1)Context環境類:環境類中維護一個State對象,他是定義了當前的狀態

(2)State抽象狀態類

(3)ConcreteState具體狀態類:每一個類封裝了一個狀態對應的行為

3、代碼實現

在狀態模式中,我們主要是對一個對象的不同狀態做出不同的響應即可,所以我們需要分析清楚對象的所有狀態,然後進行封裝即可。我們將酒店房間作為對象,假定分別有預定,入住、空閑,三種狀態,然後進行實現即可。

(1)定義每一種狀態,先構建一個狀態接口,然後對所有狀態加以實現此接口

public interface State {  //提供一個處理方法  public void handle();}
/** * 預定狀態 */public class BookState implements State {  @Override  public void handle() {    System.out.println("房間處於已經被預定的狀態,等待入住!");  }}
/** * 已經入住的狀態 */public class CheckedInState implements State{  @Override  public void handle() {    System.out.println("房間已經入住,請勿打擾!");  }}
/** * 房間屬於空閑狀態 */public class FreeState implements State {  @Override  public void handle() {    System.out.println("房間還沒有被預定,處於空閑狀態,現在可以預定!");  }}

(2)對環境變量Context的構建

/** * 上下文文本,類似於房間類,主要用來代表當前狀態 */public class Context {  private State s ;  public void setS(State s) {    System.out.println("狀態修改中!");    this.s = s;    s.handle();  }}

tips:對於上面的環境變量,我們可以將其理解為房間類,一個Context類就代表着一個房間,然後通過setState()方法,更改房間的狀態,通過傳遞不同的state,更改私有屬性State,然後完成對象的不同狀態。上述代碼中的類圖關係如下:

(3)測試代碼

public class Client {  public static void main(String[] args) {    Context context = new Context();    context.setS(new BookState());    context.setS(new FreeState());    context.setS(new CheckedInState());  }}

結果圖:

tips:通過實現的代碼可以看出,對於不同的狀態,我們都是單獨封裝成類,然後通過傳遞給上下文context(相當於是房間對象)進行上下調用的。

十、觀察者模式(Observer)

1、核心

(1)觀察者模式主要用於1:N的通知。當一個對象(目標對象Subject或Objservable)的狀態變化時,他需要及時告知一系列對象(觀察者對象,Observer),令他們做出響應

(2)通知觀察者的方式:

-推:每次都會把通知以廣播方式發送給所有觀察者,所有觀察者只能被動接受。

-拉:觀察者只要知道有情況即可。至於什麼時候獲取內容,獲取什麼內容,都可以自主決定。

2、代碼實現

與其他模式相同,分析模式的需求之後,我們首先可以確認的就是有兩個對象,一個是目標對象subject和另一個對象observer。在每一個對象的建立之前,我們需要依次建立響應的接口,以便於後期整個代碼的擴展。

(1)首先創建一個觀察者接口,以及實現此接口的具體觀察者類

public interface Observer {  public void update(Subject subject); }
public class ObserverA implements Observer {  private int myState;  @Override  public void update(Subject subject) {    this.myState = ((ConcreteSubject)subject).getState();  }  public int getMyState() {    return myState;  }}

tips:在觀察者接口中,我們只提供一個必須實現的update()方法,可以有多個不同的觀察者類實現該接口,並且在不同的觀察者類中,可以根據自己的需求增加不同的方法。此處我們為了簡潔介紹,僅僅實現了一個觀察者類ObserverA,額外增加了一個getMyState的方法,便於外部調用。

(2)創建一個廣播者類,以及繼承抽閑廣播類的具體廣播者

public class Subject {  private List<Observer> list = new ArrayList<Observer>();  public void registorObserver(Observer obs) {    list.add(obs);    }  public void removeObserver(Observer obs) {    list.remove(obs);  }  public void notifyAllObserver() {    for (Observer observer : list) {      observer.update(this);    }  }}
public class ConcreteSubject extends Subject{  private int state;  public int getState() {    return state;  }  public void setState(int state) {    this.state = state;    //主題對象(目標對象)的值發生了變化,請通知所有的觀察者    this.notifyAllObserver();  }}

tips:在目標對象的創建中,由於每個目標對象都會有大量相同的對象,所以我們選擇構建一個類的方法,將所有重複的代碼方法放在父類中,在子類中增加自己所需要的方法即可。經過上述代碼的創建類對象創建之後,可以得到每個類對象的邏輯關係如下所示:

(3)測試代碼

public class Client {  public static void main(String[] args) {    //創建目標對象    ConcreteSubject subject = new ConcreteSubject();      //創建多個觀察者    ObserverA obs1 = new ObserverA();    ObserverA obs2 = new ObserverA();    ObserverA obs3 = new ObserverA();      //將這三個觀察者添加到subject對象的觀察者隊伍中    subject.registorObserver(obs1);    subject.registorObserver(obs2);    subject.registorObserver(obs3);      //改變subject的狀態    subject.setState(300);    System.out.println("################");    System.out.println(obs1.getMyState());    System.out.println(obs2.getMyState());    System.out.println(obs3.getMyState());      //改變subject的狀態    subject.setState(30);    System.out.println("################");    System.out.println(obs1.getMyState());    System.out.println(obs2.getMyState());    System.out.println(obs3.getMyState());  } }

結果圖:

tips:在每一次代碼調用目標對象subject的setState()方法之後,所有觀察者的自身狀態都會與目標對象進行同步。將目標對象的狀態同步給觀察者對象。

3、開發中常見的場景

(1)聊天室程序,服務器轉發給所有客戶端

(2)網絡遊戲(多人聯機對戰)場景中,服務器將客戶端的狀態進行分發

(3)郵件訂閱

(4)servlet中,監聽器的實現

(5)Android中,廣播機制

十一、備忘錄模式(memento)

1、場景

(1)Word文檔編輯時,忽然電腦死機或斷電,再打開時,可以看到Word提示你回復到以前的文檔。

(2)管理系統中,公文撤回功能。公文發送出去後,想撤回來。

2、核心

保存某個對象內部狀態的拷貝,這樣以後就可以將該對象恢復到原先的狀態。

3、結構

(1)源發器類Originator

(2)備忘錄類Memento

(3)負責人類CareTaker:負責保存好的備忘錄對象,可以通過增加容器,設置多個「備忘點」。當備忘點較多時:可以將備忘錄壓棧,以及將多個備忘錄對象,序列化和持久化。

4、代碼實現

我們根據上述結構,實現備忘錄模式的流程。

(1)源發器類Originator

public class Emp {  private String name;  private int age;  private double salary;    //進行備忘操作,並返回備忘錄對象  public EmpMemento memento() {    return new EmpMemento(this);  }    //恢復為某一個指定的備忘錄  public void recovery(EmpMemento mmt) {    this.name = mmt.getName();    this.age = mmt.getAge();    this.salary = mmt.getSalary();  }    public Emp(String name, int age, double salary) {    super();    this.name = name;    this.age = age;    this.salary = salary;  }  public String getName() {    return name;  }  public void setName(String name) {    this.name = name;  }  public int getAge() {    return age;  }  public void setAge(int age) {    this.age = age;  }  public double getSalary() {    return salary;  }  public void setSalary(double salary) {    this.salary = salary;  }}

(2)備忘錄類Memento

public class EmpMemento {    private String name;  private int age;  private double salary;    public EmpMemento(Emp emp) {    this.age = emp.getAge();    this.salary = emp.getSalary();    this.name = emp.getName();  }  public String getName() {    return name;  }  public void setName(String name) {    this.name = name;  }  public int getAge() {    return age;  }  public void setAge(int age) {    this.age = age;  }  public double getSalary() {    return salary;  }  public void setSalary(double salary) {    this.salary = salary;  }}

(3)負責人類CareTaker,管理備忘錄

public class CareTaker {  private EmpMemento mmt;  public EmpMemento getMmt() {    return mmt;  }  public void setMmt(EmpMemento mmt) {    this.mmt = mmt;  }}

tips:上述備忘錄模式的三個基本類構建好之後,其相關之間的關係如以下類圖所示:

(4)測試代碼

public class Client {  public static void main(String[] args) {    CareTaker taker = new CareTaker();    Emp emp = new Emp("peng",18,9000);    System.out.println("第一次設置:"+"---"+emp.getName()+"---"+emp.getAge()+"---"+emp.getSalary());        taker.setMmt(emp.memento());    emp.setName("peng2");    emp.setAge(20);    emp.setSalary(15000);    System.out.println("第二次設置:"+"---"+emp.getName()+"---"+emp.getAge()+"---"+emp.getSalary());        emp.recovery(taker.getMmt());//恢復備忘錄    System.out.println("第三次設置:"+"---"+emp.getName()+"---"+emp.getAge()+"---"+emp.getSalary());  }}

查看結果圖:

tips:測試代碼中,我們首先新建一個emp之後,對其基本屬性進行設置,然後將其存檔,重新設置基本屬性值,最後通過備忘錄將其恢復。得到的結果與我們預想的結果完全相同。由此完成備忘錄模式的模擬。