設(shè)計(jì)模式概要(三)

行為型模式用于描述程序在運(yùn)行時(shí)復(fù)雜的流程控制,即描述多個(gè)類或?qū)ο笾g怎樣相互協(xié)作共同完成單個(gè)對(duì)象都無法單獨(dú)完成的任務(wù),它涉及算法與對(duì)象間職責(zé)的分配。
是設(shè)計(jì)模式分類當(dāng)中種類最多的。

1、責(zé)任鏈模式

顧名思義,責(zé)任鏈模式(Chain of Responsibility Pattern)為請(qǐng)求創(chuàng)建了一個(gè)接收者對(duì)象的鏈。這種模式給予請(qǐng)求的類型,對(duì)請(qǐng)求的發(fā)送者和接收者進(jìn)行解耦。這種類型的設(shè)計(jì)模式屬于行為型模式。

在這種模式中,通常每個(gè)接收者都包含對(duì)另一個(gè)接收者的引用。如果一個(gè)對(duì)象不能處理該請(qǐng)求,那么它會(huì)把相同的請(qǐng)求傳給下一個(gè)接收者,依此類推。

介紹

意圖:避免請(qǐng)求發(fā)送者與接收者耦合在一起,讓多個(gè)對(duì)象都有可能接收請(qǐng)求,將這些對(duì)象連接成一條鏈,并且沿著這條鏈傳遞請(qǐng)求,直到有對(duì)象處理它為止。

主要解決:職責(zé)鏈上的處理者負(fù)責(zé)處理請(qǐng)求,客戶只需要將請(qǐng)求發(fā)送到職責(zé)鏈上即可,無須關(guān)心請(qǐng)求的處理細(xì)節(jié)和請(qǐng)求的傳遞,所以職責(zé)鏈將請(qǐng)求的發(fā)送者和請(qǐng)求的處理者解耦了。

何時(shí)使用:在處理消息的時(shí)候以過濾很多道。

如何解決:攔截的類都實(shí)現(xiàn)統(tǒng)一接口。

關(guān)鍵代碼:Handler 里面聚合它自己,在 HandlerRequest 里判斷是否合適,如果沒達(dá)到條件則向下傳遞,向誰傳遞之前 set 進(jìn)去。

應(yīng)用實(shí)例: 1、紅樓夢中的"擊鼓傳花"。 2、JS 中的事件冒泡。 3、JAVA WEB 中 Apache Tomcat 對(duì) Encoding 的處理,Struts2 的攔截器,jsp servlet 的 Filter。

優(yōu)點(diǎn): 1、降低耦合度。它將請(qǐng)求的發(fā)送者和接收者解耦。 2、簡化了對(duì)象。使得對(duì)象不需要知道鏈的結(jié)構(gòu)。 3、增強(qiáng)給對(duì)象指派職責(zé)的靈活性。通過改變鏈內(nèi)的成員或者調(diào)動(dòng)它們的次序,允許動(dòng)態(tài)地新增或者刪除責(zé)任。 4、增加新的請(qǐng)求處理類很方便。

缺點(diǎn): 1、不能保證請(qǐng)求一定被接收。 2、系統(tǒng)性能將受到一定影響,而且在進(jìn)行代碼調(diào)試時(shí)不太方便,可能會(huì)造成循環(huán)調(diào)用。 3、可能不容易觀察運(yùn)行時(shí)的特征,有礙于除錯(cuò)。

使用場景: 1、有多個(gè)對(duì)象可以處理同一個(gè)請(qǐng)求,具體哪個(gè)對(duì)象處理該請(qǐng)求由運(yùn)行時(shí)刻自動(dòng)確定。 2、在不明確指定接收者的情況下,向多個(gè)對(duì)象中的一個(gè)提交一個(gè)請(qǐng)求。 3、可動(dòng)態(tài)指定一組對(duì)象處理請(qǐng)求。

注意事項(xiàng):在 JAVA WEB 中遇到很多應(yīng)用。

實(shí)現(xiàn)

我們創(chuàng)建抽象類 AbstractLogger,帶有詳細(xì)的日志記錄級(jí)別。然后我們創(chuàng)建三種類型的記錄器,都擴(kuò)展了 AbstractLogger。每個(gè)記錄器消息的級(jí)別是否屬于自己的級(jí)別,如果是則相應(yīng)地打印出來,否則將不打印并把消息傳給下一個(gè)記錄器。

責(zé)任鏈模式的 UML 圖

步驟 1
創(chuàng)建抽象的記錄器類。

AbstractLogger.java
public abstract class AbstractLogger {
   public static int INFO = 1;
   public static int DEBUG = 2;
   public static int ERROR = 3;
 
   protected int level;
 
   //責(zé)任鏈中的下一個(gè)元素
   protected AbstractLogger nextLogger;
 
   public void setNextLogger(AbstractLogger nextLogger){
      this.nextLogger = nextLogger;
   }
 
   public void logMessage(int level, String message){
      if(this.level <= level){
         write(message);
      }
      if(nextLogger !=null){
         nextLogger.logMessage(level, message);
      }
   }
 
   abstract protected void write(String message);
   
}

步驟 2
創(chuàng)建擴(kuò)展了該記錄器類的實(shí)體類。

ConsoleLogger.java
public class ConsoleLogger extends AbstractLogger {
 
   public ConsoleLogger(int level){
      this.level = level;
   }
 
   @Override
   protected void write(String message) {    
      System.out.println("Standard Console::Logger: " + message);
   }
}
ErrorLogger.java
public class ErrorLogger extends AbstractLogger {
 
   public ErrorLogger(int level){
      this.level = level;
   }
 
   @Override
   protected void write(String message) {    
      System.out.println("Error Console::Logger: " + message);
   }
}
FileLogger.java
public class FileLogger extends AbstractLogger {
 
   public FileLogger(int level){
      this.level = level;
   }
 
   @Override
   protected void write(String message) {    
      System.out.println("File::Logger: " + message);
   }
}

步驟 3
創(chuàng)建不同類型的記錄器。賦予它們不同的錯(cuò)誤級(jí)別,并在每個(gè)記錄器中設(shè)置下一個(gè)記錄器。每個(gè)記錄器中的下一個(gè)記錄器代表的是鏈的一部分。

ChainPatternDemo.java
public class ChainPatternDemo {
   
   private static AbstractLogger getChainOfLoggers(){
 
      AbstractLogger errorLogger = new ErrorLogger(AbstractLogger.ERROR);
      AbstractLogger fileLogger = new FileLogger(AbstractLogger.DEBUG);
      AbstractLogger consoleLogger = new ConsoleLogger(AbstractLogger.INFO);
 
      errorLogger.setNextLogger(fileLogger);
      fileLogger.setNextLogger(consoleLogger);
 
      return errorLogger;  
   }
 
   public static void main(String[] args) {
      AbstractLogger loggerChain = getChainOfLoggers();
 
      loggerChain.logMessage(AbstractLogger.INFO, "This is an information.");
 
      loggerChain.logMessage(AbstractLogger.DEBUG, 
         "This is a debug level information.");
 
      loggerChain.logMessage(AbstractLogger.ERROR, 
         "This is an error information.");
   }
}

責(zé)任鏈模式其實(shí)非常多的應(yīng)用在流當(dāng)中,在范例中的代碼里運(yùn)用了鏈表的數(shù)據(jù)結(jié)構(gòu),一次次的遍歷到結(jié)尾,每次都會(huì)查看是否能夠處理本次遍歷的結(jié)果。如果不能處理就往后繼續(xù)看是否能處理,這當(dāng)然也可以用流來書寫,如果用流的話,那最典型的應(yīng)用就是Filter了。這種處理方式能夠大大減少你的代碼量,如果你要自己書寫的話你可能會(huì)需要很多次的判斷,而這里只需要采用這種設(shè)計(jì)模式就可以解決。
注意的是,采用這種設(shè)計(jì)模式,通常都是能夠無需在意給你的數(shù)據(jù)類型的順序結(jié)構(gòu),只需要遍歷完畢就可以了。

2、模板模式

在模板模式(Template Pattern)中,一個(gè)抽象類公開定義了執(zhí)行它的方法的方式/模板。它的子類可以按需要重寫方法實(shí)現(xiàn),但調(diào)用將以抽象類中定義的方式進(jìn)行。這種類型的設(shè)計(jì)模式屬于行為型模式。

介紹

意圖:定義一個(gè)操作中的算法的骨架,而將一些步驟延遲到子類中。模板方法使得子類可以不改變一個(gè)算法的結(jié)構(gòu)即可重定義該算法的某些特定步驟。

主要解決:一些方法通用,卻在每一個(gè)子類都重新寫了這一方法。

何時(shí)使用:有一些通用的方法。

如何解決:將這些通用算法抽象出來。

關(guān)鍵代碼:在抽象類實(shí)現(xiàn),其他步驟在子類實(shí)現(xiàn)。

應(yīng)用實(shí)例: 1、在造房子的時(shí)候,地基、走線、水管都一樣,只有在建筑的后期才有加壁櫥加?xùn)艡诘炔町悺?2、西游記里面菩薩定好的 81 難,這就是一個(gè)頂層的邏輯骨架。 3、spring 中對(duì) Hibernate 的支持,將一些已經(jīng)定好的方法封裝起來,比如開啟事務(wù)、獲取 Session、關(guān)閉 Session 等,程序員不重復(fù)寫那些已經(jīng)規(guī)范好的代碼,直接丟一個(gè)實(shí)體就可以保存。

優(yōu)點(diǎn): 1、封裝不變部分,擴(kuò)展可變部分。 2、提取公共代碼,便于維護(hù)。 3、行為由父類控制,子類實(shí)現(xiàn)。

缺點(diǎn):每一個(gè)不同的實(shí)現(xiàn)都需要一個(gè)子類來實(shí)現(xiàn),導(dǎo)致類的個(gè)數(shù)增加,使得系統(tǒng)更加龐大。

使用場景: 1、有多個(gè)子類共有的方法,且邏輯相同。 2、重要的、復(fù)雜的方法,可以考慮作為模板方法。

注意事項(xiàng):為防止惡意操作,一般模板方法都加上 final 關(guān)鍵詞。

實(shí)現(xiàn)

我們將創(chuàng)建一個(gè)定義操作的 Game 抽象類,其中,模板方法設(shè)置為 final,這樣它就不會(huì)被重寫。CricketFootball 是擴(kuò)展了 Game 的實(shí)體類,它們重寫了抽象類的方法。

TemplatePatternDemo,我們的演示類使用 Game 來演示模板模式的用法。

模板模式的 UML 圖

步驟 1
創(chuàng)建一個(gè)抽象類,它的模板方法被設(shè)置為 final。

Game.java
public abstract class Game {
   abstract void initialize();
   abstract void startPlay();
   abstract void endPlay();
 
   //模板
   public final void play(){
 
      //初始化游戲
      initialize();
 
      //開始游戲
      startPlay();
 
      //結(jié)束游戲
      endPlay();
   }
}

步驟 2
創(chuàng)建擴(kuò)展了上述類的實(shí)體類。

Cricket.java
public class Cricket extends Game {
 
   @Override
   void endPlay() {
      System.out.println("Cricket Game Finished!");
   }
 
   @Override
   void initialize() {
      System.out.println("Cricket Game Initialized! Start playing.");
   }
 
   @Override
   void startPlay() {
      System.out.println("Cricket Game Started. Enjoy the game!");
   }
}
Football.java
public class Football extends Game {
 
   @Override
   void endPlay() {
      System.out.println("Football Game Finished!");
   }
 
   @Override
   void initialize() {
      System.out.println("Football Game Initialized! Start playing.");
   }
 
   @Override
   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();      
   }
}

模板模式,正如他的名字,就是提供一個(gè)模板,在頂層類中提供一個(gè)默認(rèn)方法去調(diào)用抽象方法。實(shí)體類去實(shí)現(xiàn)抽象方法,然后使用的時(shí)候就直接調(diào)用默認(rèn)方法就可以了。思想很簡單,也很容易實(shí)現(xiàn)。

3.命令模式

命令模式(Command Pattern)是一種數(shù)據(jù)驅(qū)動(dòng)的設(shè)計(jì)模式,它屬于行為型模式。請(qǐng)求以命令的形式包裹在對(duì)象中,并傳給調(diào)用對(duì)象。調(diào)用對(duì)象尋找可以處理該命令的合適的對(duì)象,并把該命令傳給相應(yīng)的對(duì)象,該對(duì)象執(zhí)行命令。

介紹

意圖:將一個(gè)請(qǐng)求封裝成一個(gè)對(duì)象,從而使您可以用不同的請(qǐng)求對(duì)客戶進(jìn)行參數(shù)化。

主要解決:在軟件系統(tǒng)中,行為請(qǐng)求者與行為實(shí)現(xiàn)者通常是一種緊耦合的關(guān)系,但某些場合,比如需要對(duì)行為進(jìn)行記錄、撤銷或重做、事務(wù)等處理時(shí),這種無法抵御變化的緊耦合的設(shè)計(jì)就不太合適。

何時(shí)使用:在某些場合,比如要對(duì)行為進(jìn)行"記錄、撤銷/重做、事務(wù)"等處理,這種無法抵御變化的緊耦合是不合適的。在這種情況下,如何將"行為請(qǐng)求者"與"行為實(shí)現(xiàn)者"解耦?將一組行為抽象為對(duì)象,可以實(shí)現(xiàn)二者之間的松耦合。

如何解決:通過調(diào)用者調(diào)用接受者執(zhí)行命令,順序:調(diào)用者→接受者→命令。

關(guān)鍵代碼:定義三個(gè)角色:1、received 真正的命令執(zhí)行對(duì)象 2、Command 3、invoker 使用命令對(duì)象的入口

應(yīng)用實(shí)例:struts 1 中的 action 核心控制器 ActionServlet 只有一個(gè),相當(dāng)于 Invoker,而模型層的類會(huì)隨著不同的應(yīng)用有不同的模型類,相當(dāng)于具體的 Command。

優(yōu)點(diǎn): 1、降低了系統(tǒng)耦合度。 2、新的命令可以很容易添加到系統(tǒng)中去。

缺點(diǎn):使用命令模式可能會(huì)導(dǎo)致某些系統(tǒng)有過多的具體命令類。

使用場景:認(rèn)為是命令的地方都可以使用命令模式,比如: 1、GUI 中每一個(gè)按鈕都是一條命令。 2、模擬 CMD。

注意事項(xiàng):系統(tǒng)需要支持命令的撤銷(Undo)操作和恢復(fù)(Redo)操作,也可以考慮使用命令模式,見命令模式的擴(kuò)展。

實(shí)現(xiàn)

我們首先創(chuàng)建作為命令的接口 Order,然后創(chuàng)建作為請(qǐng)求的 Stock 類。實(shí)體命令類 BuyStockSellStock,實(shí)現(xiàn)了 Order 接口,將執(zhí)行實(shí)際的命令處理。創(chuàng)建作為調(diào)用對(duì)象的類 Broker,它接受訂單并能下訂單。

Broker 對(duì)象使用命令模式,基于命令的類型確定哪個(gè)對(duì)象執(zhí)行哪個(gè)命令。CommandPatternDemo,我們的演示類使用 Broker 類來演示命令模式。

命令模式的 UML 圖

步驟 1
創(chuàng)建一個(gè)命令接口。

Order.java
public interface Order {
   void execute();
}

步驟 2
創(chuàng)建一個(gè)請(qǐng)求類。

Stock.java
public class Stock {
   
   private String name = "ABC";
   private int quantity = 10;
 
   public void buy(){
      System.out.println("Stock [ Name: "+name+", 
         Quantity: " + quantity +" ] bought");
   }
   public void sell(){
      System.out.println("Stock [ Name: "+name+", 
         Quantity: " + quantity +" ] sold");
   }
}

步驟 3
創(chuàng)建實(shí)現(xiàn)了 Order 接口的實(shí)體類。

BuyStock.java
public class BuyStock implements Order {
   private Stock abcStock;
 
   public BuyStock(Stock abcStock){
      this.abcStock = abcStock;
   }
 
   public void execute() {
      abcStock.buy();
   }
}
SellStock.java
public class SellStock implements Order {
   private Stock abcStock;
 
   public SellStock(Stock abcStock){
      this.abcStock = abcStock;
   }
 
   public void execute() {
      abcStock.sell();
   }
}

步驟 4
創(chuàng)建命令調(diào)用類。

Broker.java
import java.util.ArrayList;
import java.util.List;
 
public class Broker {
   private List<Order> orderList = new ArrayList<Order>(); 
 
   public void takeOrder(Order order){
      orderList.add(order);      
   }
 
   public void placeOrders(){
      for (Order order : orderList) {
         order.execute();
      }
      orderList.clear();
   }
}

步驟 5
使用 Broker 類來接受并執(zhí)行命令。

CommandPatternDemo.java
public class CommandPatternDemo {
   public static void main(String[] args) {
      Stock abcStock = new Stock();
 
      BuyStock buyStockOrder = new BuyStock(abcStock);
      SellStock sellStockOrder = new SellStock(abcStock);
 
      Broker broker = new Broker();
      broker.takeOrder(buyStockOrder);
      broker.takeOrder(sellStockOrder);
 
      broker.placeOrders();
   }
}

命令模式一個(gè)將命令存儲(chǔ)在對(duì)象中的模式,他可以將命令對(duì)象傳遞給調(diào)用者,然后我們?nèi)ナ褂谜{(diào)用者來調(diào)用。這個(gè)模式結(jié)構(gòu)是比較清晰的,目的就是為了解耦,其實(shí)設(shè)計(jì)模式學(xué)了這么久,百分之50的設(shè)計(jì)都是為了解耦。

4、解釋器模式

解釋器模式(Interpreter Pattern)提供了評(píng)估語言的語法或表達(dá)式的方式,它屬于行為型模式。這種模式實(shí)現(xiàn)了一個(gè)表達(dá)式接口,該接口解釋一個(gè)特定的上下文。這種模式被用在 SQL 解析、符號(hào)處理引擎等。

介紹

意圖:給定一個(gè)語言,定義它的文法表示,并定義一個(gè)解釋器,這個(gè)解釋器使用該標(biāo)識(shí)來解釋語言中的句子。

主要解決:對(duì)于一些固定文法構(gòu)建一個(gè)解釋句子的解釋器。

何時(shí)使用:如果一種特定類型的問題發(fā)生的頻率足夠高,那么可能就值得將該問題的各個(gè)實(shí)例表述為一個(gè)簡單語言中的句子。這樣就可以構(gòu)建一個(gè)解釋器,該解釋器通過解釋這些句子來解決該問題。

如何解決:構(gòu)建語法樹,定義終結(jié)符與非終結(jié)符。

關(guān)鍵代碼:構(gòu)建環(huán)境類,包含解釋器之外的一些全局信息,一般是 HashMap。

應(yīng)用實(shí)例:編譯器、運(yùn)算表達(dá)式計(jì)算。

優(yōu)點(diǎn): 1、可擴(kuò)展性比較好,靈活。 2、增加了新的解釋表達(dá)式的方式。 3、易于實(shí)現(xiàn)簡單文法。

缺點(diǎn): 1、可利用場景比較少。 2、對(duì)于復(fù)雜的文法比較難維護(hù)。 3、解釋器模式會(huì)引起類膨脹。 4、解釋器模式采用遞歸調(diào)用方法。

使用場景: 1、可以將一個(gè)需要解釋執(zhí)行的語言中的句子表示為一個(gè)抽象語法樹。 2、一些重復(fù)出現(xiàn)的問題可以用一種簡單的語言來進(jìn)行表達(dá)。 3、一個(gè)簡單語法需要解釋的場景。

注意事項(xiàng):可利用場景比較少,JAVA 中如果碰到可以用 expression4J 代替。

實(shí)現(xiàn)

我們將創(chuàng)建一個(gè)接口 Expression 和實(shí)現(xiàn)了 Expression 接口的實(shí)體類。定義作為上下文中主要解釋器的 TerminalExpression 類。其他的類 OrExpression、AndExpression 用于創(chuàng)建組合式表達(dá)式。

InterpreterPatternDemo,我們的演示類使用 Expression 類創(chuàng)建規(guī)則和演示表達(dá)式的解析。

解釋器模式的 UML 圖

步驟 1
創(chuàng)建一個(gè)表達(dá)式接口。

Expression.java
public interface Expression {
   public boolean interpret(String context);
}

步驟 2
創(chuàng)建實(shí)現(xiàn)了上述接口的實(shí)體類。

TerminalExpression.java
public class TerminalExpression implements Expression {
   
   private String data;
 
   public TerminalExpression(String data){
      this.data = data; 
   }
 
   @Override
   public boolean interpret(String context) {
      if(context.contains(data)){
         return true;
      }
      return false;
   }
}
OrExpression.java
public class OrExpression implements Expression {
    
   private Expression expr1 = null;
   private Expression expr2 = null;
 
   public OrExpression(Expression expr1, Expression expr2) { 
      this.expr1 = expr1;
      this.expr2 = expr2;
   }
 
   @Override
   public boolean interpret(String context) {      
      return expr1.interpret(context) || expr2.interpret(context);
   }
}
AndExpression.java
public class AndExpression implements Expression {
    
   private Expression expr1 = null;
   private Expression expr2 = null;
 
   public AndExpression(Expression expr1, Expression expr2) { 
      this.expr1 = expr1;
      this.expr2 = expr2;
   }
 
   @Override
   public boolean interpret(String context) {      
      return expr1.interpret(context) && expr2.interpret(context);
   }
}

步驟 3
InterpreterPatternDemo 使用 Expression 類來創(chuàng)建規(guī)則,并解析它們。

InterpreterPatternDemo.java
public class InterpreterPatternDemo {
 
   //規(guī)則:Robert 和 John 是男性
   public static Expression getMaleExpression(){
      Expression robert = new TerminalExpression("Robert");
      Expression john = new TerminalExpression("John");
      return new OrExpression(robert, john);    
   }
 
   //規(guī)則:Julie 是一個(gè)已婚的女性
   public static Expression getMarriedWomanExpression(){
      Expression julie = new TerminalExpression("Julie");
      Expression married = new TerminalExpression("Married");
      return new AndExpression(julie, married);    
   }
 
   public static void main(String[] args) {
      Expression isMale = getMaleExpression();
      Expression isMarriedWoman = getMarriedWomanExpression();
 
      System.out.println("John is male? " + isMale.interpret("John"));
      System.out.println("Julie is a married women? " 
      + isMarriedWoman.interpret("Married Julie"));
   }
}

解釋器模式是一個(gè)使用的比較少的模式,它首先先創(chuàng)建了一個(gè)接口,這是一個(gè)抽象表達(dá)式,里面有interpret方法,然后會(huì)有兩種實(shí)現(xiàn)類,一種是終結(jié)符表達(dá)式,另外一種是非終結(jié)符表達(dá)式。然后便可以使用了,例子其實(shí)舉的一般,意思理解即可。主要是確實(shí)也很難找到一個(gè)很好的例子,畢竟這種模式使用的頻率較少。

5、迭代器模式

迭代器模式(Iterator Pattern)是 Java 和 .Net 編程環(huán)境中非常常用的設(shè)計(jì)模式。這種模式用于順序訪問集合對(duì)象的元素,不需要知道集合對(duì)象的底層表示。

迭代器模式屬于行為型模式。

介紹

意圖:提供一種方法順序訪問一個(gè)聚合對(duì)象中各個(gè)元素, 而又無須暴露該對(duì)象的內(nèi)部表示。

主要解決:不同的方式來遍歷整個(gè)整合對(duì)象。

何時(shí)使用:遍歷一個(gè)聚合對(duì)象。

如何解決:把在元素之間游走的責(zé)任交給迭代器,而不是聚合對(duì)象。

關(guān)鍵代碼:定義接口:hasNext, next。

應(yīng)用實(shí)例:JAVA 中的 iterator。

優(yōu)點(diǎn): 1、它支持以不同的方式遍歷一個(gè)聚合對(duì)象。 2、迭代器簡化了聚合類。 3、在同一個(gè)聚合上可以有多個(gè)遍歷。 4、在迭代器模式中,增加新的聚合類和迭代器類都很方便,無須修改原有代碼。

缺點(diǎn):由于迭代器模式將存儲(chǔ)數(shù)據(jù)和遍歷數(shù)據(jù)的職責(zé)分離,增加新的聚合類需要對(duì)應(yīng)增加新的迭代器類,類的個(gè)數(shù)成對(duì)增加,這在一定程度上增加了系統(tǒng)的復(fù)雜性。

使用場景: 1、訪問一個(gè)聚合對(duì)象的內(nèi)容而無須暴露它的內(nèi)部表示。 2、需要為聚合對(duì)象提供多種遍歷方式。 3、為遍歷不同的聚合結(jié)構(gòu)提供一個(gè)統(tǒng)一的接口。

注意事項(xiàng):迭代器模式就是分離了集合對(duì)象的遍歷行為,抽象出一個(gè)迭代器類來負(fù)責(zé),這樣既可以做到不暴露集合的內(nèi)部結(jié)構(gòu),又可讓外部代碼透明地訪問集合內(nèi)部的數(shù)據(jù)。

實(shí)現(xiàn)

我們將創(chuàng)建一個(gè)敘述導(dǎo)航方法的 Iterator 接口和一個(gè)返回迭代器的 Container 接口。實(shí)現(xiàn)了 Container 接口的實(shí)體類將負(fù)責(zé)實(shí)現(xiàn) Iterator 接口。

IteratorPatternDemo,我們的演示類使用實(shí)體類 NamesRepository 來打印 NamesRepository 中存儲(chǔ)為集合的 Names

迭代器模式的 UML 圖

步驟 1
創(chuàng)建接口:

Iterator.java
public interface Iterator {
   public boolean hasNext();
   public Object next();
}
Container.java
public interface Container {
   public Iterator getIterator();
}

步驟 2
創(chuàng)建實(shí)現(xiàn)了 Container 接口的實(shí)體類。該類有實(shí)現(xiàn)了 Iterator 接口的內(nèi)部類 NameIterator。

NameRepository.java
public class NameRepository implements Container {
   public String names[] = {"Robert" , "John" ,"Julie" , "Lora"};
 
   @Override
   public Iterator getIterator() {
      return new NameIterator();
   }
 
   private class NameIterator implements Iterator {
 
      int index;
 
      @Override
      public boolean hasNext() {
         if(index < names.length){
            return true;
         }
         return false;
      }
 
      @Override
      public Object next() {
         if(this.hasNext()){
            return names[index++];
         }
         return null;
      }     
   }
}

步驟 3
使用 NameRepository 來獲取迭代器,并打印名字。

IteratorPatternDemo.java
public class IteratorPatternDemo {
   
   public static void main(String[] args) {
      NameRepository namesRepository = new NameRepository();
 
      for(Iterator iter = namesRepository.getIterator(); iter.hasNext();){
         String name = (String)iter.next();
         System.out.println("Name : " + name);
      }  
   }
}

迭代器模式,是一個(gè)你沒學(xué)過設(shè)計(jì)模式也聽說過,知道的一個(gè)模式,這個(gè)設(shè)計(jì)模式太有名了,因?yàn)槲覀兯褂玫娜萜鳎际亲裱髂J蕉O(shè)計(jì)的,我們用迭代器模式一般都是不用在意如何去遍歷一個(gè)容器,我們更加去在意遍歷的結(jié)果,而不需要在意如何去遍歷,因此我們可以用一個(gè)迭代器去幫我們遍歷,我們不需要在意如何去遍歷的, 也許這個(gè)遍歷過程很復(fù)雜比如一個(gè)二叉樹,我們只需要調(diào)用next方法就可以完成遍歷,這就是我們用迭代器的意義所在。

6、 中介者模式

中介者模式(Mediator Pattern)是用來降低多個(gè)對(duì)象和類之間的通信復(fù)雜性。這種模式提供了一個(gè)中介類,該類通常處理不同類之間的通信,并支持松耦合,使代碼易于維護(hù)。中介者模式屬于行為型模式。

介紹

意圖:用一個(gè)中介對(duì)象來封裝一系列的對(duì)象交互,中介者使各對(duì)象不需要顯式地相互引用,從而使其耦合松散,而且可以獨(dú)立地改變它們之間的交互。

主要解決:對(duì)象與對(duì)象之間存在大量的關(guān)聯(lián)關(guān)系,這樣勢必會(huì)導(dǎo)致系統(tǒng)的結(jié)構(gòu)變得很復(fù)雜,同時(shí)若一個(gè)對(duì)象發(fā)生改變,我們也需要跟蹤與之相關(guān)聯(lián)的對(duì)象,同時(shí)做出相應(yīng)的處理。

何時(shí)使用:多個(gè)類相互耦合,形成了網(wǎng)狀結(jié)構(gòu)。

如何解決:將上述網(wǎng)狀結(jié)構(gòu)分離為星型結(jié)構(gòu)。

關(guān)鍵代碼:對(duì)象 Colleague 之間的通信封裝到一個(gè)類中單獨(dú)處理。

應(yīng)用實(shí)例: 1、中國加入 WTO 之前是各個(gè)國家相互貿(mào)易,結(jié)構(gòu)復(fù)雜,現(xiàn)在是各個(gè)國家通過 WTO 來互相貿(mào)易。 2、機(jī)場調(diào)度系統(tǒng)。 3、MVC 框架,其中C(控制器)就是 M(模型)和 V(視圖)的中介者。

優(yōu)點(diǎn): 1、降低了類的復(fù)雜度,將一對(duì)多轉(zhuǎn)化成了一對(duì)一。 2、各個(gè)類之間的解耦。 3、符合迪米特原則。

缺點(diǎn):中介者會(huì)龐大,變得復(fù)雜難以維護(hù)。

使用場景: 1、系統(tǒng)中對(duì)象之間存在比較復(fù)雜的引用關(guān)系,導(dǎo)致它們之間的依賴關(guān)系結(jié)構(gòu)混亂而且難以復(fù)用該對(duì)象。 2、想通過一個(gè)中間類來封裝多個(gè)類中的行為,而又不想生成太多的子類。

注意事項(xiàng):不應(yīng)當(dāng)在職責(zé)混亂的時(shí)候使用。

實(shí)現(xiàn)

我們通過聊天室實(shí)例來演示中介者模式。實(shí)例中,多個(gè)用戶可以向聊天室發(fā)送消息,聊天室向所有的用戶顯示消息。我們將創(chuàng)建兩個(gè)類 ChatRoomUser。User 對(duì)象使用 ChatRoom 方法來分享他們的消息。

MediatorPatternDemo,我們的演示類使用 User 對(duì)象來顯示他們之間的通信。

中介者模式的 UML 圖

步驟 1
創(chuàng)建中介類。

ChatRoom.java
import java.util.Date;
 
public class ChatRoom {
   public static void showMessage(User user, String message){
      System.out.println(new Date().toString()
         + " [" + user.getName() +"] : " + message);
   }
}

步驟 2
創(chuàng)建 user 類。

User.java
public class User {
   private String name;
 
   public String getName() {
      return name;
   }
 
   public void setName(String name) {
      this.name = name;
   }
 
   public User(String name){
      this.name  = name;
   }
 
   public void sendMessage(String message){
      ChatRoom.showMessage(this,message);
   }
}

步驟 3
使用 User 對(duì)象來顯示他們之間的通信。

MediatorPatternDemo.java
public class MediatorPatternDemo {
   public static void main(String[] args) {
      User robert = new User("Robert");
      User john = new User("John");
 
      robert.sendMessage("Hi! John!");
      john.sendMessage("Hello! Robert!");
   }
}

中介者模式同樣是一個(gè)比較容易理解的模式,做web項(xiàng)目我們都知道需要用三層架構(gòu),service層,dao層,web層,目的究竟是什么呢?其實(shí)很簡單,降低成本。
我們可以舉一個(gè)現(xiàn)實(shí)生活中的模型,生產(chǎn)商,代理商,和客戶。生產(chǎn)商如果既做生產(chǎn),他如果也要做售出商品的服務(wù),那成本無疑會(huì)上漲很多,因?yàn)樯a(chǎn)商他只是單純擅長生產(chǎn),他不擅長銷售,他就要把任務(wù)給代理銷售的人,比如淘寶,京東,讓他們這些平臺(tái)代理銷售,處理售后服務(wù),這樣各司其職,效率能夠上升,客戶端不和生產(chǎn)商打交道,只用和代理商打交道就可以了。
而在程序中也是這樣,service層把各種dao層的基礎(chǔ)查詢更新操作融合在一起進(jìn)行處理,這樣可以維護(hù)起來更方便,結(jié)構(gòu)更清晰的同時(shí),效率也有保障。
因此在程序設(shè)計(jì)中,中介者模式也是一個(gè)非常重要的模式。

7、備忘錄模式

備忘錄模式(Memento Pattern)保存一個(gè)對(duì)象的某個(gè)狀態(tài),以便在適當(dāng)?shù)臅r(shí)候恢復(fù)對(duì)象。備忘錄模式屬于行為型模式。

介紹

意圖:在不破壞封裝性的前提下,捕獲一個(gè)對(duì)象的內(nèi)部狀態(tài),并在該對(duì)象之外保存這個(gè)狀態(tài)。

主要解決:所謂備忘錄模式就是在不破壞封裝的前提下,捕獲一個(gè)對(duì)象的內(nèi)部狀態(tài),并在該對(duì)象之外保存這個(gè)狀態(tài),這樣可以在以后將對(duì)象恢復(fù)到原先保存的狀態(tài)。

何時(shí)使用:很多時(shí)候我們總是需要記錄一個(gè)對(duì)象的內(nèi)部狀態(tài),這樣做的目的就是為了允許用戶取消不確定或者錯(cuò)誤的操作,能夠恢復(fù)到他原先的狀態(tài),使得他有"后悔藥"可吃。

如何解決:通過一個(gè)備忘錄類專門存儲(chǔ)對(duì)象狀態(tài)。

關(guān)鍵代碼:客戶不與備忘錄類耦合,與備忘錄管理類耦合。

應(yīng)用實(shí)例: 1、后悔藥。 2、打游戲時(shí)的存檔。 3、Windows 里的 ctri + z。 4、IE 中的后退。 4、數(shù)據(jù)庫的事務(wù)管理。

優(yōu)點(diǎn): 1、給用戶提供了一種可以恢復(fù)狀態(tài)的機(jī)制,可以使用戶能夠比較方便地回到某個(gè)歷史的狀態(tài)。 2、實(shí)現(xiàn)了信息的封裝,使得用戶不需要關(guān)心狀態(tài)的保存細(xì)節(jié)。

缺點(diǎn):消耗資源。如果類的成員變量過多,勢必會(huì)占用比較大的資源,而且每一次保存都會(huì)消耗一定的內(nèi)存。

使用場景: 1、需要保存/恢復(fù)數(shù)據(jù)的相關(guān)狀態(tài)場景。 2、提供一個(gè)可回滾的操作。

注意事項(xiàng): 1、為了符合迪米特原則,還要增加一個(gè)管理備忘錄的類。 2、為了節(jié)約內(nèi)存,可使用原型模式+備忘錄模式。

實(shí)現(xiàn)

備忘錄模式使用三個(gè)類 Memento、OriginatorCareTaker。Memento 包含了要被恢復(fù)的對(duì)象的狀態(tài)。Originator 創(chuàng)建并在 Memento 對(duì)象中存儲(chǔ)狀態(tài)。Caretaker 對(duì)象負(fù)責(zé)從 Memento 中恢復(fù)對(duì)象的狀態(tài)。

MementoPatternDemo,我們的演示類使用 CareTakerOriginator 對(duì)象來顯示對(duì)象的狀態(tài)恢復(fù)。

備忘錄模式的 UML 圖

步驟 1
創(chuàng)建 Memento 類。

Memento.java
public class Memento {
   private String state;
 
   public Memento(String state){
      this.state = state;
   }
 
   public String getState(){
      return state;
   }  
}

步驟 2
創(chuàng)建 Originator 類。

Originator.java
public class Originator {
   private String state;
 
   public void setState(String state){
      this.state = state;
   }
 
   public String getState(){
      return state;
   }
 
   public Memento saveStateToMemento(){
      return new Memento(state);
   }
 
   public void getStateFromMemento(Memento Memento){
      state = Memento.getState();
   }
}

步驟 3
創(chuàng)建 CareTaker 類。

CareTaker.java
import java.util.ArrayList;
import java.util.List;
 
public class CareTaker {
   private List<Memento> mementoList = new ArrayList<Memento>();
 
   public void add(Memento state){
      mementoList.add(state);
   }
 
   public Memento get(int index){
      return mementoList.get(index);
   }
}

步驟 4
使用 CareTaker 和 Originator 對(duì)象。

MementoPatternDemo.java
public class MementoPatternDemo {
   public static void main(String[] args) {
      Originator originator = new Originator();
      CareTaker careTaker = new CareTaker();
      originator.setState("State #1");
      originator.setState("State #2");
      careTaker.add(originator.saveStateToMemento());
      originator.setState("State #3");
      careTaker.add(originator.saveStateToMemento());
      originator.setState("State #4");
 
      System.out.println("Current State: " + originator.getState());    
      originator.getStateFromMemento(careTaker.get(0));
      System.out.println("First saved State: " + originator.getState());
      originator.getStateFromMemento(careTaker.get(1));
      System.out.println("Second saved State: " + originator.getState());
   }
}

以前一直好奇ctrl+z是怎么完成撤銷的, 理解了這個(gè)模式,也就理解撤銷的過程。它實(shí)際上就是將之前的狀態(tài)統(tǒng)統(tǒng)保存了下來,然后可以通過操作進(jìn)行返回之前狀態(tài)。這個(gè)模式,在實(shí)際的操作中應(yīng)該是比較重要的,但是自己實(shí)現(xiàn)起來好像有點(diǎn)復(fù)雜。

8、觀察者模式

當(dāng)對(duì)象間存在一對(duì)多關(guān)系時(shí),則使用觀察者模式(Observer Pattern)。比如,當(dāng)一個(gè)對(duì)象被修改時(shí),則會(huì)自動(dòng)通知依賴它的對(duì)象。觀察者模式屬于行為型模式。

介紹

意圖:定義對(duì)象間的一種一對(duì)多的依賴關(guān)系,當(dāng)一個(gè)對(duì)象的狀態(tài)發(fā)生改變時(shí),所有依賴于它的對(duì)象都得到通知并被自動(dòng)更新。

主要解決:一個(gè)對(duì)象狀態(tài)改變給其他對(duì)象通知的問題,而且要考慮到易用和低耦合,保證高度的協(xié)作。

何時(shí)使用:一個(gè)對(duì)象(目標(biāo)對(duì)象)的狀態(tài)發(fā)生改變,所有的依賴對(duì)象(觀察者對(duì)象)都將得到通知,進(jìn)行廣播通知。

如何解決:使用面向?qū)ο蠹夹g(shù),可以將這種依賴關(guān)系弱化。

關(guān)鍵代碼:在抽象類里有一個(gè) ArrayList 存放觀察者們。

應(yīng)用實(shí)例: 1、拍賣的時(shí)候,拍賣師觀察最高標(biāo)價(jià),然后通知給其他競價(jià)者競價(jià)。 2、西游記里面悟空請(qǐng)求菩薩降服紅孩兒,菩薩灑了一地水招來一個(gè)老烏龜,這個(gè)烏龜就是觀察者,他觀察菩薩灑水這個(gè)動(dòng)作。

優(yōu)點(diǎn): 1、觀察者和被觀察者是抽象耦合的。 2、建立一套觸發(fā)機(jī)制。

缺點(diǎn): 1、如果一個(gè)被觀察者對(duì)象有很多的直接和間接的觀察者的話,將所有的觀察者都通知到會(huì)花費(fèi)很多時(shí)間。 2、如果在觀察者和觀察目標(biāo)之間有循環(huán)依賴的話,觀察目標(biāo)會(huì)觸發(fā)它們之間進(jìn)行循環(huán)調(diào)用,可能導(dǎo)致系統(tǒng)崩潰。 3、觀察者模式?jīng)]有相應(yīng)的機(jī)制讓觀察者知道所觀察的目標(biāo)對(duì)象是怎么發(fā)生變化的,而僅僅只是知道觀察目標(biāo)發(fā)生了變化。

使用場景:

  • 一個(gè)抽象模型有兩個(gè)方面,其中一個(gè)方面依賴于另一個(gè)方面。將這些方面封裝在獨(dú)立的對(duì)象中使它們可以各自獨(dú)立地改變和復(fù)用。
  • 一個(gè)對(duì)象的改變將導(dǎo)致其他一個(gè)或多個(gè)對(duì)象也發(fā)生改變,而不知道具體有多少對(duì)象將發(fā)生改變,可以降低對(duì)象之間的耦合度。
  • 一個(gè)對(duì)象必須通知其他對(duì)象,而并不知道這些對(duì)象是誰。
  • 需要在系統(tǒng)中創(chuàng)建一個(gè)觸發(fā)鏈,A對(duì)象的行為將影響B(tài)對(duì)象,B對(duì)象的行為將影響C對(duì)象……,可以使用觀察者模式創(chuàng)建一種鏈?zhǔn)接|發(fā)機(jī)制。

注意事項(xiàng): 1、JAVA 中已經(jīng)有了對(duì)觀察者模式的支持類。 2、避免循環(huán)引用。 3、如果順序執(zhí)行,某一觀察者錯(cuò)誤會(huì)導(dǎo)致系統(tǒng)卡殼,一般采用異步方式。

實(shí)現(xiàn)

觀察者模式使用三個(gè)類 Subject、Observer 和 Client。Subject 對(duì)象帶有綁定觀察者到 Client 對(duì)象和從 Client 對(duì)象解綁觀察者的方法。我們創(chuàng)建 Subject 類、Observer 抽象類和擴(kuò)展了抽象類 Observer 的實(shí)體類。

ObserverPatternDemo,我們的演示類使用 Subject 和實(shí)體類對(duì)象來演示觀察者模式。

觀察者模式的 UML 圖

步驟 1
創(chuàng)建 Subject 類。

Subject.java
import java.util.ArrayList;
import java.util.List;
 
public class Subject {
   
   private List<Observer> observers 
      = new ArrayList<Observer>();
   private int state;
 
   public int getState() {
      return state;
   }
 
   public void setState(int state) {
      this.state = state;
      notifyAllObservers();
   }
 
   public void attach(Observer observer){
      observers.add(observer);      
   }
 
   public void notifyAllObservers(){
      for (Observer observer : observers) {
         observer.update();
      }
   }  
}

步驟 2
創(chuàng)建 Observer 類。

Observer.java
public abstract class Observer {
   protected Subject subject;
   public abstract void update();
}

步驟 3
創(chuàng)建實(shí)體觀察者類。

BinaryObserver.java
public class BinaryObserver extends Observer{
 
   public BinaryObserver(Subject subject){
      this.subject = subject;
      this.subject.attach(this);
   }
 
   @Override
   public void update() {
      System.out.println( "Binary String: " 
      + Integer.toBinaryString( subject.getState() ) ); 
   }
}
OctalObserver.java
public class OctalObserver extends Observer{
 
   public OctalObserver(Subject subject){
      this.subject = subject;
      this.subject.attach(this);
   }
 
   @Override
   public void update() {
     System.out.println( "Octal String: " 
     + Integer.toOctalString( subject.getState() ) ); 
   }
}
HexaObserver.java
public class HexaObserver extends Observer{
 
   public HexaObserver(Subject subject){
      this.subject = subject;
      this.subject.attach(this);
   }
 
   @Override
   public void update() {
      System.out.println( "Hex String: " 
      + Integer.toHexString( subject.getState() ).toUpperCase() ); 
   }
}

步驟 4
使用 Subject 和實(shí)體觀察者對(duì)象。

ObserverPatternDemo.java
public class ObserverPatternDemo {
   public static void main(String[] args) {
      Subject subject = new Subject();
 
      new HexaObserver(subject);
      new OctalObserver(subject);
      new BinaryObserver(subject);
 
      System.out.println("First state change: 15");   
      subject.setState(15);
      System.out.println("Second state change: 10");  
      subject.setState(10);
   }
}

觀察者模式其實(shí)思想和實(shí)現(xiàn)方式都不算復(fù)雜,就是通過一個(gè)類保存所有對(duì)象用集合方式,然后每次更新對(duì)象就把集合內(nèi)的所有內(nèi)容全部更新一次,需要注意的是,這些對(duì)象是不必互相認(rèn)識(shí)的,只要繼承相同類就可以保存在一個(gè)集合當(dāng)中,是非常方便的。

9、 狀態(tài)模式

在狀態(tài)模式(State Pattern)中,類的行為是基于它的狀態(tài)改變的。這種類型的設(shè)計(jì)模式屬于行為型模式。

在狀態(tài)模式中,我們創(chuàng)建表示各種狀態(tài)的對(duì)象和一個(gè)行為隨著狀態(tài)對(duì)象改變而改變的 context 對(duì)象。

介紹

意圖:允許對(duì)象在內(nèi)部狀態(tài)發(fā)生改變時(shí)改變它的行為,對(duì)象看起來好像修改了它的類。

主要解決:對(duì)象的行為依賴于它的狀態(tài)(屬性),并且可以根據(jù)它的狀態(tài)改變而改變它的相關(guān)行為。

何時(shí)使用:代碼中包含大量與對(duì)象狀態(tài)有關(guān)的條件語句。

如何解決:將各種具體的狀態(tài)類抽象出來。

關(guān)鍵代碼:通常命令模式的接口中只有一個(gè)方法。而狀態(tài)模式的接口中有一個(gè)或者多個(gè)方法。而且,狀態(tài)模式的實(shí)現(xiàn)類的方法,一般返回值,或者是改變實(shí)例變量的值。也就是說,狀態(tài)模式一般和對(duì)象的狀態(tài)有關(guān)。實(shí)現(xiàn)類的方法有不同的功能,覆蓋接口中的方法。狀態(tài)模式和命令模式一樣,也可以用于消除 if...else 等條件選擇語句。

應(yīng)用實(shí)例: 1、打籃球的時(shí)候運(yùn)動(dòng)員可以有正常狀態(tài)、不正常狀態(tài)和超常狀態(tài)。 2、曾侯乙編鐘中,'鐘是抽象接口','鐘A'等是具體狀態(tài),'曾侯乙編鐘'是具體環(huán)境(Context)。

優(yōu)點(diǎn): 1、封裝了轉(zhuǎn)換規(guī)則。 2、枚舉可能的狀態(tài),在枚舉狀態(tài)之前需要確定狀態(tài)種類。 3、將所有與某個(gè)狀態(tài)有關(guān)的行為放到一個(gè)類中,并且可以方便地增加新的狀態(tài),只需要改變對(duì)象狀態(tài)即可改變對(duì)象的行為。 4、允許狀態(tài)轉(zhuǎn)換邏輯與狀態(tài)對(duì)象合成一體,而不是某一個(gè)巨大的條件語句塊。 5、可以讓多個(gè)環(huán)境對(duì)象共享一個(gè)狀態(tài)對(duì)象,從而減少系統(tǒng)中對(duì)象的個(gè)數(shù)。

缺點(diǎn): 1、狀態(tài)模式的使用必然會(huì)增加系統(tǒng)類和對(duì)象的個(gè)數(shù)。 2、狀態(tài)模式的結(jié)構(gòu)與實(shí)現(xiàn)都較為復(fù)雜,如果使用不當(dāng)將導(dǎo)致程序結(jié)構(gòu)和代碼的混亂。 3、狀態(tài)模式對(duì)"開閉原則"的支持并不太好,對(duì)于可以切換狀態(tài)的狀態(tài)模式,增加新的狀態(tài)類需要修改那些負(fù)責(zé)狀態(tài)轉(zhuǎn)換的源代碼,否則無法切換到新增狀態(tài),而且修改某個(gè)狀態(tài)類的行為也需修改對(duì)應(yīng)類的源代碼。

使用場景: 1、行為隨狀態(tài)改變而改變的場景。 2、條件、分支語句的代替者。

注意事項(xiàng):在行為受狀態(tài)約束的時(shí)候使用狀態(tài)模式,而且狀態(tài)不超過 5 個(gè)。

實(shí)現(xiàn)

我們將創(chuàng)建一個(gè) State 接口和實(shí)現(xiàn)了 State 接口的實(shí)體狀態(tài)類。Context 是一個(gè)帶有某個(gè)狀態(tài)的類。

StatePatternDemo,我們的演示類使用 Context 和狀態(tài)對(duì)象來演示 Context 在狀態(tài)改變時(shí)的行為變化。

狀態(tài)模式的 UML 圖

步驟 1
創(chuàng)建一個(gè)接口。

State.java
public interface State {
   public void doAction(Context context);
}

步驟 2
創(chuàng)建實(shí)現(xiàn)接口的實(shí)體類。

StartState.java
public 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";
   }
}
StopState.java
public 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";
   }
}

步驟 3
創(chuàng)建 Context 類。

Context.java
public class Context {
   private State state;
 
   public Context(){
      state = null;
   }
 
   public void setState(State state){
      this.state = state;     
   }
 
   public State getState(){
      return state;
   }
}

步驟 4
使用 Context 來查看當(dāng)狀態(tài) State 改變時(shí)的行為變化。

StatePatternDemo.java
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());
   }
}

狀態(tài)模式同樣是一個(gè)非常容易理解的設(shè)計(jì)模式,就是根據(jù)不同的狀態(tài)行為也不一樣,似乎和命令模式是有點(diǎn)類似的,根據(jù)不同命令來做不同的事。但實(shí)際上一般情況下,狀態(tài)模式需要一個(gè)抽象接口,然后有多個(gè)不同的實(shí)例去實(shí)現(xiàn)狀態(tài)接口,就是所謂的狀態(tài),與命令模式還是有些區(qū)別,但是兩者都是實(shí)現(xiàn)了if...else...的功能。

10、 訪問者模式

在訪問者模式(Visitor Pattern)中,我們使用了一個(gè)訪問者類,它改變了元素類的執(zhí)行算法。通過這種方式,元素的執(zhí)行算法可以隨著訪問者改變而改變。這種類型的設(shè)計(jì)模式屬于行為型模式。根據(jù)模式,元素對(duì)象已接受訪問者對(duì)象,這樣訪問者對(duì)象就可以處理元素對(duì)象上的操作。

介紹

意圖:主要將數(shù)據(jù)結(jié)構(gòu)與數(shù)據(jù)操作分離。

主要解決:穩(wěn)定的數(shù)據(jù)結(jié)構(gòu)和易變的操作耦合問題。

何時(shí)使用:需要對(duì)一個(gè)對(duì)象結(jié)構(gòu)中的對(duì)象進(jìn)行很多不同的并且不相關(guān)的操作,而需要避免讓這些操作"污染"這些對(duì)象的類,使用訪問者模式將這些封裝到類中。

如何解決:在被訪問的類里面加一個(gè)對(duì)外提供接待訪問者的接口。

關(guān)鍵代碼:在數(shù)據(jù)基礎(chǔ)類里面有一個(gè)方法接受訪問者,將自身引用傳入訪問者。

應(yīng)用實(shí)例:您在朋友家做客,您是訪問者,朋友接受您的訪問,您通過朋友的描述,然后對(duì)朋友的描述做出一個(gè)判斷,這就是訪問者模式。

優(yōu)點(diǎn): 1、符合單一職責(zé)原則。 2、優(yōu)秀的擴(kuò)展性。 3、靈活性。

缺點(diǎn): 1、具體元素對(duì)訪問者公布細(xì)節(jié),違反了迪米特原則。 2、具體元素變更比較困難。 3、違反了依賴倒置原則,依賴了具體類,沒有依賴抽象。

使用場景: 1、對(duì)象結(jié)構(gòu)中對(duì)象對(duì)應(yīng)的類很少改變,但經(jīng)常需要在此對(duì)象結(jié)構(gòu)上定義新的操作。 2、需要對(duì)一個(gè)對(duì)象結(jié)構(gòu)中的對(duì)象進(jìn)行很多不同的并且不相關(guān)的操作,而需要避免讓這些操作"污染"這些對(duì)象的類,也不希望在增加新操作時(shí)修改這些類。

注意事項(xiàng):訪問者可以對(duì)功能進(jìn)行統(tǒng)一,可以做報(bào)表、UI、攔截器與過濾器。

實(shí)現(xiàn)

我們將創(chuàng)建一個(gè)定義接受操作的 ComputerPart 接口。Keyboard、Mouse、MonitorComputer 是實(shí)現(xiàn)了 ComputerPart 接口的實(shí)體類。我們將定義另一個(gè)接口 ComputerPartVisitor,它定義了訪問者類的操作。Computer 使用實(shí)體訪問者來執(zhí)行相應(yīng)的動(dòng)作。

VisitorPatternDemo,我們的演示類使用 Computer、ComputerPartVisitor 類來演示訪問者模式的用法。

訪問者模式的 UML 圖

步驟 1
定義一個(gè)表示元素的接口。

ComputerPart.java
public interface ComputerPart {
   public void accept(ComputerPartVisitor computerPartVisitor);
}

步驟 2
創(chuàng)建擴(kuò)展了上述類的實(shí)體類。

Keyboard.java
public class Keyboard  implements ComputerPart {
 
   @Override
   public void accept(ComputerPartVisitor computerPartVisitor) {
      computerPartVisitor.visit(this);
   }
}
Monitor.java
public class Monitor  implements ComputerPart {
 
   @Override
   public void accept(ComputerPartVisitor computerPartVisitor) {
      computerPartVisitor.visit(this);
   }
}
Mouse.java
public class Mouse  implements ComputerPart {
 
   @Override
   public void accept(ComputerPartVisitor computerPartVisitor) {
      computerPartVisitor.visit(this);
   }
}
Computer.java
public class Computer implements ComputerPart {
   
   ComputerPart[] parts;
 
   public Computer(){
      parts = new ComputerPart[] {new Mouse(), new Keyboard(), new Monitor()};      
   } 
 
 
   @Override
   public void accept(ComputerPartVisitor computerPartVisitor) {
      for (int i = 0; i < parts.length; i++) {
         parts[i].accept(computerPartVisitor);
      }
      computerPartVisitor.visit(this);
   }
}

步驟 3
定義一個(gè)表示訪問者的接口。

ComputerPartVisitor.java
public interface ComputerPartVisitor {
   public void visit(Computer computer);
   public void visit(Mouse mouse);
   public void visit(Keyboard keyboard);
   public void visit(Monitor monitor);
}

步驟 4
創(chuàng)建實(shí)現(xiàn)了上述類的實(shí)體訪問者。

ComputerPartDisplayVisitor.java
public class ComputerPartDisplayVisitor implements ComputerPartVisitor {
 
   @Override
   public void visit(Computer computer) {
      System.out.println("Displaying Computer.");
   }
 
   @Override
   public void visit(Mouse mouse) {
      System.out.println("Displaying Mouse.");
   }
 
   @Override
   public void visit(Keyboard keyboard) {
      System.out.println("Displaying Keyboard.");
   }
 
   @Override
   public void visit(Monitor monitor) {
      System.out.println("Displaying Monitor.");
   }
}

步驟 5
使用 ComputerPartDisplayVisitor 來顯示 Computer 的組成部分。

VisitorPatternDemo.java
public class VisitorPatternDemo {
   public static void main(String[] args) {
 
      ComputerPart computer = new Computer();
      computer.accept(new ComputerPartDisplayVisitor());
   }
}

訪問者模式是一種非常復(fù)雜的模式,當(dāng)我們需要對(duì)一個(gè)對(duì)象訪問,而不同人關(guān)注的點(diǎn)不一樣的時(shí)候,就可以使用這個(gè)設(shè)計(jì)模式,我們首先有一個(gè)抽象類或者抽象接口接受一個(gè)對(duì)象,而通過不同的對(duì)象,我們展示的內(nèi)容也是不一樣的, 但是需要知道的與狀態(tài)模式是有明顯區(qū)別的,一個(gè)是相同對(duì)象的不同狀態(tài),一個(gè)是不同的對(duì)象。看似差不多的模式,但是實(shí)際應(yīng)用會(huì)完全不一樣,只能說思想是類似的,畢竟都是行為型設(shè)計(jì)模式。

11、策略模式

在策略模式(Strategy Pattern)中,一個(gè)類的行為或其算法可以在運(yùn)行時(shí)更改。這種類型的設(shè)計(jì)模式屬于行為型模式。

在策略模式中,我們創(chuàng)建表示各種策略的對(duì)象和一個(gè)行為隨著策略對(duì)象改變而改變的 context 對(duì)象。策略對(duì)象改變 context 對(duì)象的執(zhí)行算法。

介紹

意圖:定義一系列的算法,把它們一個(gè)個(gè)封裝起來, 并且使它們可相互替換。

主要解決:在有多種算法相似的情況下,使用 if...else 所帶來的復(fù)雜和難以維護(hù)。

何時(shí)使用:一個(gè)系統(tǒng)有許多許多類,而區(qū)分它們的只是他們直接的行為。

如何解決:將這些算法封裝成一個(gè)一個(gè)的類,任意地替換。

關(guān)鍵代碼:實(shí)現(xiàn)同一個(gè)接口。

應(yīng)用實(shí)例: 1、諸葛亮的錦囊妙計(jì),每一個(gè)錦囊就是一個(gè)策略。 2、旅行的出游方式,選擇騎自行車、坐汽車,每一種旅行方式都是一個(gè)策略。 3、JAVA AWT 中的 LayoutManager。

優(yōu)點(diǎn): 1、算法可以自由切換。 2、避免使用多重條件判斷。 3、擴(kuò)展性良好。

缺點(diǎn): 1、策略類會(huì)增多。 2、所有策略類都需要對(duì)外暴露。

使用場景: 1、如果在一個(gè)系統(tǒng)里面有許多類,它們之間的區(qū)別僅在于它們的行為,那么使用策略模式可以動(dòng)態(tài)地讓一個(gè)對(duì)象在許多行為中選擇一種行為。 2、一個(gè)系統(tǒng)需要?jiǎng)討B(tài)地在幾種算法中選擇一種。 3、如果一個(gè)對(duì)象有很多的行為,如果不用恰當(dāng)?shù)哪J?,這些行為就只好使用多重的條件選擇語句來實(shí)現(xiàn)。

注意事項(xiàng):如果一個(gè)系統(tǒng)的策略多于四個(gè),就需要考慮使用混合模式,解決策略類膨脹的問題。

實(shí)現(xiàn)

我們將創(chuàng)建一個(gè)定義活動(dòng)的 Strategy 接口和實(shí)現(xiàn)了 Strategy 接口的實(shí)體策略類。Context 是一個(gè)使用了某種策略的類。

StrategyPatternDemo,我們的演示類使用 Context 和策略對(duì)象來演示 Context 在它所配置或使用的策略改變時(shí)的行為變化。

策略模式的 UML 圖

步驟 1
創(chuàng)建一個(gè)接口。

Strategy.java
public interface Strategy {
   public int doOperation(int num1, int num2);
}

步驟 2
創(chuàng)建實(shí)現(xiàn)接口的實(shí)體類。

OperationAdd.java
public class OperationAdd implements Strategy{
   @Override
   public int doOperation(int num1, int num2) {
      return num1 + num2;
   }
}
OperationSubtract.java
public class OperationSubtract implements Strategy{
   @Override
   public int doOperation(int num1, int num2) {
      return num1 - num2;
   }
}
OperationMultiply.java
public class OperationMultiply implements Strategy{
   @Override
   public int doOperation(int num1, int num2) {
      return num1 * num2;
   }
}

步驟 3
創(chuàng)建 Context 類。

Context.java
public class Context {
   private Strategy strategy;
 
   public Context(Strategy strategy){
      this.strategy = strategy;
   }
 
   public int executeStrategy(int num1, int num2){
      return strategy.doOperation(num1, num2);
   }
}

步驟 4
使用 Context 來查看當(dāng)它改變策略 Strategy 時(shí)的行為變化。

StrategyPatternDemo.java
public class StrategyPatternDemo {
   public static void main(String[] args) {
      Context context = new Context(new OperationAdd());    
      System.out.println("10 + 5 = " + context.executeStrategy(10, 5));
 
      context = new Context(new OperationSubtract());      
      System.out.println("10 - 5 = " + context.executeStrategy(10, 5));
 
      context = new Context(new OperationMultiply());    
      System.out.println("10 * 5 = " + context.executeStrategy(10, 5));
   }
}

與之前幾種設(shè)計(jì)模式是類似的,就是為了解決if...else...的方式,一個(gè)抽象類中有一個(gè)操作方法,這個(gè)抽象類可以被多種類實(shí)例化,然后可以通過選擇不同的對(duì)象來執(zhí)行不同的方法。而這種設(shè)計(jì)模式與之前幾種的著重點(diǎn)不同,更加著重的是策略,或者說是方法本身。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容