狀態模式(State pattern)

pamxy發表於2013-09-27

轉自:http://www.cnblogs.com/singlepine/archive/2005/11/03/268380.html

一、引子 

  狀態模式自身結構非常簡單——前面剛剛介紹了幾個結構比較簡單的設計模式,和他們一樣,狀態模式在具體實現上留下了可變換的餘地。我前面已經介紹過它的孿生兄妹策略模式了,大家可以兩者比較著閱讀。本文將會討論兩者的區別。

  二、定義與結構

  GOF《設計模式》中給狀態模式下的定義為:允許一個物件在其內部狀態改變時改變它的行為。這個物件看起來似乎修改了它的類。看起來,狀態模式好像是神通廣大——居然能夠“修改自身的類”!

  能夠讓程式根據不同的外部情況來做出不同的響應,最直接的方法就是在程式中將這些可能發生的外部情況全部考慮到,使用if else 語句來進行程式碼響應選擇。但是這種方法對於複雜一點的狀態判斷,就會顯得雜亂無章,容易產生錯誤;而且增加一個新的狀態將會帶來大量的修改。這個時候“能夠修改自身”的狀態模式的引入也許是個不錯的主意。

  狀態模式可以有效的替換充滿在程式中的if else語句:將不同條件下的行為封裝在一個類裡面,再給這些類一個統一的父類來約束他們。來看一下狀態模式的角色組成吧:

  1) 使用環境(Context)角色:客戶程式是通過它來滿足自己的需求。它定義了客戶程式需要的介面;並且維護一個具體狀態角色的例項,這個例項來決定當前的狀態。 

  2) 狀態(State)角色:定義一個介面以封裝與使用環境角色的一個特定狀態相關的行為。

  3) 具體狀態(Concrete State)角色:實現狀態角色定義的介面。

  類圖如下,結構非常簡單也與策略模式非常相似。

 

 


  三、實現

  由於狀態模式結構非常簡單,所以在這裡羅列一些反映狀態模式實現結構的程式碼沒有什麼太大的作用。如果你有興趣的話可以按照上面類圖來編寫一下。

  在引子中已經提到,狀態模式在具體實現上存在不同的方案。因此這裡重點就這些不同的實現方式進行介紹和討論。

  首先,實現時是否將狀態角色、具體狀態角色暴露給客戶程式?按照GOF的建議是不希望將狀態角色暴露給客戶程式的,與客戶程式打交道的僅僅是使用環境角色,客戶是不知道系統是怎麼實現的,更不關心什麼有幾個具體狀態。但是當使用環境角色中的初始狀態緊緊依賴於客戶程式時,適乎暴露是在所難免的——這就與策略模式異常相似了!

  具體狀態角色中的行為一般是與使用環境角色密切相關的。因此這裡便有了一個小細節:我們把使用環境角色作為引數傳遞進入具體狀態角色後,是在具體狀態角色中來實現狀態響應行為;還是僅僅呼叫在使用環境角色中已經實現了的方法?由於這些行為往往與使用環境角色相關,所以按照《重構》一書的“指導”——後一種實現方法是比較地道的。

  從定義可知,狀態模式是要應對狀態轉換的。那麼狀態的轉換在哪裡定義呢?你可以選擇在使用環境角色的程式碼中來表現出來,當然這便意味著狀態轉變的規則就固定下來了。GOF還給出了另外一種稍微靈活一點的實現方式:在每一個具體狀態角色中來指定後續狀態以及何時進行轉換。

  其實在java強大的反射機制的支援下,我們還可以將狀態的轉換做的更加靈活——我們可以將狀態轉換的規則寫在.xml等等的配置檔案裡面甚至是資料庫中,我們姑且叫做狀態轉換表。進行轉換前,根據狀態轉換表來讀取下一個狀態,然後利用反射獲得具體的狀態物件……。看起來很不錯的樣子,只是效率可能低一些,在企業應用中這應該不是最重要的。 

  狀態模式已經被我們想象著“實現”了一番。那麼狀態模式的引入會給我們的程式帶來哪些優勢呢?前面我們已經說過:狀態模式的引入免除了程式碼中複雜而庸長的邏輯判斷語句。而且具體狀態角色將具體狀態和它對應的行為封裝了起來,這使得增加一種新的狀態變得簡單一些。而且如果設計合理得話,具體狀態角色可以被重用(和策略模式一樣,可以考慮使用享元模式來實現)。

  使用狀態模式也會帶來一些問題。每個狀態對應一個具體的狀態類,使得整體分散,邏輯不太清晰。當然對於一個狀態非常多的系統,狀態模式帶來的優點還是大於它的缺點的。

  由上面的分析就可以很明確的知道什麼時候該使用狀態模式了。下面是GOF在《設計模式》中給出的狀態模式的適用情況: 

  1) 一個物件的行為取決於它的狀態, 並且它必須在執行時刻根據狀態改變它的行為。

  2) 一個操作中含有龐大的多分支的條件語句,且這些分支依賴於該物件的狀態。

  四、狀態VS策略

  仔細對比狀態模式和策略模式,難免會產生疑問:這兩個明明是一個東西嘛!下面我們就來分析下兩者區別。

  首先我要宣告,在實際應用中只要能夠使得你的程式碼靈活漂亮起來,何必計較這些方方面面的差別呢?

  Brandon Goldfedder在《模式的樂趣》裡是怎麼說的:“strategy模式在結構上與state模式非常相似,但是在概念上,他們的目的差異非常大。區分這兩個模式的關鍵是看行為是由狀態驅動還是由一組演算法驅動,這條規則似乎有點隨意,但是在判斷時還是需要考慮它。通常,State模式的“狀態”是在物件內部的,Strategy模式的“策略”可以在物件外部,不過這也不是一條嚴格、可靠的規則。”

  我很同意Brandon Goldfedder的觀點。這兩個模式的劃分,就在於使用的目的是不同的——策略模式用來處理演算法變化,而狀態模式則是處理狀態變化(好玄乎阿)。

  策略模式中,演算法是否變化完全是由客戶程式開決定的,而且往往一次只能選擇一種演算法,不存在演算法中途發生變化的情況。從《深入淺出策略模式》中的例子可以很好的看出。

  而狀態模式如定義中所言,在它的生命週期中存在著狀態的轉變和行為得更改,而且狀態變化是一個線性的整體;對於客戶程式來言,這種狀態變化往往是透明的。
using System; 
 
namespace DesignPattern.State 

    
/**//// <summary> 
    
/// 抽象狀態 狀態介面 
    
/// </summary> 

    public interface ITcpState 
    

        
void Open(); 
        
void Close(); 
        
void Acknowledge(); 
    }
 
 
    
/**//// <summary> 
    
/// 此類相當於Context 
    
/// </summary> 

    public class TcpConnection 
    

        
private ITcpState state; 
 
        
public ITcpState State 
        

            
set{this.state=value;} 
        }
 
 
        
public void Open() 
        

            
this.State=new TcpEstablished(); 
            
this.state.Open(); 
            
this.State=new TcpListen(); 
        }
 
 
        
public void Close() 
        

            
this.state.Close(); 
            
this.State=new TcpClosed(); 
        }
 
 
        
public void Acknowledge() 
        

            
this.state.Acknowledge(); 
        }
 
    }
 
 
    
public class TcpEstablished : ITcpState 
    

         
        
public void Open() 
        

            Console.WriteLine(
"Have opened,can not open again!"); 
        }
 
 
        
public void Close() 
        

            Console.WriteLine(
"Closing"); 
        }
 
 
        
public void Acknowledge() 
        

            Console.WriteLine(
"TcpEstablished!"); 
        }
 
 
    }
 
 
    
public class TcpListen : ITcpState 
    

        
public void Open() 
        

            Console.WriteLine(
"Have opened,can not open again!"); 
        }
 
 
        
public void Close() 
        

            Console.WriteLine(
"Closing"); 
        }
 
 
        
public void Acknowledge() 
        

            Console.WriteLine(
"TcpListen!"); 
        }
 
 
 
    }
 
 
    
public class TcpClosed : ITcpState 
    

        
public void Open() 
        

            Console.WriteLine(
"Openning"); 
        }
 
 
        
public void Close() 
        

            Console.WriteLine(
"Have closed,can not close again!"); 
        }
 
 
        
public void Acknowledge() 
        

            Console.WriteLine(
"TcpClosed!"); 
        }
 
 
    }
 
 
    
public class Client 
    

        
public static void Main() 
        

            TcpConnection tcpcon
=new TcpConnection(); 
            tcpcon.Open(); 
            tcpcon.Acknowledge(); 
            tcpcon.Open(); 
            tcpcon.Close(); 
            tcpcon.Acknowledge(); 
            tcpcon.Close(); 
        }
 
    }
 
}
 


  五、總結

  比較籠統地介紹了下狀態模式,並將它和非常相近的策略模式進行了比較。歡迎大家學習指正。

相關文章