1.概述
在軟體開發過程中,應用程式可能會根據不同的情況作出不同的處理。最直接的解決方案是將這些所有可能發生的情況全都考慮到。然後使用if... ellse語句來做狀態判斷來進行不同情況的處理。但是對複雜狀態的判斷就顯得“力不從心了”。隨著增加新的狀態或者修改一個狀體(if else(或switch case)語句的增多或者修改)可能會引起很大的修改,而程式的可讀性,擴充性也會變得很弱。維護也會很麻煩。那麼我就考慮只修改自身狀態的模式。
例子1:按鈕來控制一個電梯的狀態,一個電梯開們,關門,停,運行。每一種狀態改變,都有可能要根據其他狀態來更新處理。例如,開門狀體,你不能在啟動並執行時候開門,而是在電梯定下後才能開門。
例子2:我們給一部手機打電話,就可能出現這幾種情況:使用者開機,使用者關機,使用者欠費停機,使用者消戶等。 所以當我們撥打這個號碼的時候:系統就要判斷,該使用者是否在開機且不忙狀態,又或者是關機,欠費等狀態。但不管是那種狀態我們都應給出對應的處理操作。
2.問題
對象如何在每一種狀態下表現出不同的行為?
3.解決方案
狀態模式:允許一個對象在其內部狀態改變時改變它的行為。對象看起來似乎修改了它的類。
在很多情況下,一個對象的行為取決於一個或多個動態變化的屬性,這樣的屬性叫做狀態,這樣的對象叫做有狀態的(stateful)對象,這樣的對象狀態是從事先定義好的一系列值中取出的。當一個這樣的對象與外來事件產生互動時,其內部狀態就會改變,從而使得系統的行為也隨之發生變化。
4.樣本
先給出這個例子的類結構圖。
上面的類結構圖並不複雜,首先是抽象出一個狀態的父類,通過工作類對時間點的設定來切換不同的狀態。
邏輯結構並不複雜,還是給出簡易的代碼,大家可以慢慢體會一下。
注意:本文所有代碼均在ARC環境下編譯通過。
Work類介面
複製代碼 代碼如下:
#import <Foundation/Foundation.h>
@class State;
@interface Work :NSObject{
State *current;
}
@property double Hour;
@property BOOL TaskFinished;
-(void)SetState:(State*)s;
-(void)WriteProgram;
@end
Work類實現
複製代碼 代碼如下:
#import "Work.h"
#import "State.h"
#import "ForenoonState.h"
@implementation Work
@synthesize Hour =_Hour;
@synthesize TaskFinished =_TaskFinished;
-(id)init{
if (self == [superinit]) {
current= [[ForenoonState alloc]init];
}
return self;
}
-(void)SetState:(State *)s{
current = s;
}
-(void)WriteProgram{
[current WriteProgram:self];
}
@end
State類介面
複製代碼 代碼如下:
#import <Foundation/Foundation.h>
@class Work;
@interface State:NSObject
-(void)WriteProgram:(Work*)w;
@end
State類實現
複製代碼 代碼如下:
#import "State.h"
#import "Work.h"
@implementation State
-(void)WriteProgram:(Work *)w{
NSLog(@"目前時間:%f點下班回家了",[w Hour]);
}
@end
ForenoonState類介面
複製代碼 代碼如下:
#import "State.h"
@interface ForenoonState :State
@end
ForenoonState類實現
複製代碼 代碼如下:
#import "ForenoonState.h"
#import "Work.h"
#import "NoonState.h"
@implementation ForenoonState
-(void)WriteProgram:(Work *)w{
if ([w Hour] < 12) {
NSLog(@"目前時間:%f點上午工作,精神百倍", [w Hour]);
}
else {
[w SetState:[NoonState new]];
[w WriteProgram];
}
}
@end
NoonState類介面
複製代碼 代碼如下:
#import "State.h"
@interface NoonState:State
@end
NoonState類實現
複製代碼 代碼如下:
#import "NoonState.h"
#import "Work.h"
#import "AfternoonState.h"
@implementation NoonState
-(void)WriteProgram:(Work *)w{
if([w Hour] <13)
NSLog(@"目前時間:%f點餓了,午飯;犯困,午休",[w Hour]);
else {
[w SetState:[[AfternoonState alloc]init]];
[w WriteProgram];
}
}
@end
AfternoonState類介面
複製代碼 代碼如下:
#import "State.h"
@interface AfternoonState :State
@end
AfternoonState類實現
複製代碼 代碼如下:
#import "AfternoonState.h"
#import "Work.h"
#import "EveningState.h"
@implementation AfternoonState
-(void)WriteProgram:(Work *)w{
if ([w Hour] <17) {
NSLog(@"目前時間:%f點下午狀態還不錯,繼續努力", [w Hour]);
}
else {
[w SetState:[[EveningState alloc]init]];
[w WriteProgram];
}
}
@end
EveningState類介面
複製代碼 代碼如下:
#import "State.h"
@interface EveningState:State
@end
EveningState類實現
複製代碼 代碼如下:
#import "EveningState.h"
#import "Work.h"
#import "RestState.h"
#import "SleepingState.h"
@implementation EveningState
-(void)WriteProgram:(Work *)w{
if ([w TaskFinished]) {
[w SetState:[[RestState alloc]init]];
[w WriteProgram];
}
else {
if([w Hour] <21)
NSLog(@"目前時間:%f點加班哦,疲憊之極", [w Hour]);
else {
[w SetState:[[SleepingState alloc]init]];
[w WriteProgram];
}
}
}
@end
SleepingState類介面
複製代碼 代碼如下:
#import "State.h"
@interface SleepingState :State
@end
SleepingState類實現
複製代碼 代碼如下:
#import "SleepingState.h"
#import "Work.h"
@implementation SleepingState
-(void)WriteProgram:(Work *)w{
NSLog(@"目前時間:%f點不行了,睡著了", [w Hour]);
}
@end
RestState類介面
複製代碼 代碼如下:
#import "RestState.h"
#import "Work.h"
@implementation RestState
-(void)WriteProgram:(Work *)w{
NSLog(@"目前時間:%f點下班回家了", [w Hour]);
}
@end
Main方法調用
複製代碼 代碼如下:
#import <Foundation/Foundation.h>
#import "Work.h"
int main (int argc,const char *argv[])
{
@autoreleasepool{
Work *emergencyProjects = [[Work alloc]init];
[emergencyProjects setHour:9];
[emergencyProjects WriteProgram];
[emergencyProjects setHour:10];
[emergencyProjects WriteProgram];
[emergencyProjects setHour:12];
[emergencyProjects WriteProgram];
[emergencyProjects setHour:13];
[emergencyProjects WriteProgram];
[emergencyProjects setHour:14];
[emergencyProjects WriteProgram];
[emergencyProjects setHour:17];
[emergencyProjects WriteProgram];
[emergencyProjects setTaskFinished:NO];
[emergencyProjects setHour:19];
[emergencyProjects WriteProgram];
[emergencyProjects setHour:22];
[emergencyProjects WriteProgram];
}
return 0;
}
上面是用Objective C語言實現的簡單代碼。
通過這個例子,可以看到,狀態模式通過把各種狀態轉移邏輯分布到State的子類之間,來減少相互間的依賴。當一個對象的行為取決於它的狀態,並且它必須在運行時刻根據狀態改變它的行為時,就可以考慮使用狀態模式了。
5.適用性
在下面的兩種情況下均可使用State模式:
1) • 一個對象的行為取決於它的狀態, 並且它必須在運行時刻根據狀態改變它的行為。
2) • 代碼中包含大量與對象狀態有關的條件陳述式:一個操作中含有龐大的多分支的條件(if else(或switch case)語句,且這些分支依賴於該對象的狀態。這個狀態通常用一個或多個枚舉常量表示。通常 , 有多個操作包含這一相同的條件結構。 State模式將每一個條件分支放入一個獨立的類中。這使得你可以根據對象自身的情況將對象的狀態作為一個對象,這一對象可以不依賴於其他對象而獨立變化。
6.結構
7.模式的組成
環境類(Context): 定義客戶感興趣的介面。維護一個ConcreteState子類的執行個體,這個執行個體定義目前狀態。
抽象狀態類(State): 定義一個介面以封裝與Context的一個特定狀態相關的行為。
具體狀態類(ConcreteState): 每一子類實現一個與Context的一個狀態相關的行為。
8.效果
State模式有下面一些效果:
狀態模式的優點:
1 ) 它將與特定狀態相關的行為局部化,並且將不同狀態的行為分割開來: State模式將所有與一個特定的狀態相關的行為都放入一個對象中。因為所有與狀態相關的代碼都存在於某一個State子類中, 所以通過定義新的子類可以很容易的增加新的狀態和轉換。另一個方法是使用資料值定義內部狀態並且讓 Context操作來顯式地檢查這些資料。但這樣將會使整個Context的實現中遍布看起來很相似的條件if else語句或switch case語句。增加一個新的狀態可能需要改變若干個操作, 這就使得維護變得複雜了。State模式避免了這個問題, 但可能會引入另一個問題, 因為該模式將不同狀態的行為分布在多個State子類中。這就增加了子類的數目,相對於單個類的實現來說不夠緊湊。但是如果有許多狀態時這樣的分布實際上更好一些, 否則需要使用巨大的條件陳述式。正如很長的過程一樣,巨大的條件陳述式是不受歡迎的。它們形成一大整塊並且使得代碼不夠清晰,這又使得它們難以修改和擴充。 State模式提供了一個更好的方法來組織與特定狀態相關的代碼。決定狀態轉移的邏輯不在單塊的 i f或s w i t c h語句中, 而是分布在State子類之間。將每一個狀態轉換和動作封裝到一個類中,就把著眼點從執行狀態提高到整個對象的狀態。這將使代碼結構化並使其意圖更加清晰。
2) 它使得狀態轉換顯式化: 當一個對象僅以內部資料值來定義目前狀態時 , 其狀態僅表現為對一些變數的賦值,這不夠明確。為不同的狀態引入獨立的對象使得轉換變得更加明確。而且, State對象可保證Context不會發生內部狀態不一致的情況,因為從 Context的角度看,狀態轉換是原子的—只需重新綁定一個變數(即Context的State物件變數),而無需為多個變數賦值
3) State對象可被共用 如果State對象沒有執行個體變數—即它們表示的狀態完全以它們的類型來編碼—那麼各Context對象可以共用一個State對象。當狀態以這種方式被共用時, 它們必然是沒有內部狀態, 只有行為的輕量級對象。
狀態模式的缺點:
1) 狀態模式的使用必然會增加系統類別和對象的個數。
2) 狀態模式的結構與實現都較為複雜,如果使用不當將導致程式結構和代碼的混亂。