狀態模式(State)
狀態模式(State)主要是解決當控物一個物件狀態轉換的條件運算式過於複雜時的情況。把狀態的判斷邏輯移到表示不同狀態的一系列類別當中,可以把複雜的判斷邏輯簡化。
如果這個狀態判斷很簡單,那就沒有必要用「狀態模式」了。
結構
State抽象狀態類別,定義一個介面以封裝與Context的一個特定狀態相關的行為。ConcreteState具體狀態,每一個子類別實現一個與Context的一個狀態相關的行為。Context維護一個ConcreteState子類別的實例,這個實例定義現在的狀態。
1
2
3
4
5
6
7
8
9
10
11
12
13
Context 維護一個 ConcreteState子類別的實例,這個實例定義現在的狀態
- state
+ Request()
State 抽象狀態類別,定義一個介面以封裝與Context的一個特定狀態相關的行為
+ Handle()
ConcreteStateA 具體狀態,每一個子類別實現一個與Context的一個狀態相關的行為
+ + Handle()
ConcreteStateB
+ + Handle()
ConcreteStateC
+ + Handle()
程式碼
State抽象狀態類別
State 抽象狀態類別,定義一個介面以封裝與Context的一個特定狀態相關的行為。
1
2
3
abstract State {
public abstract void Handle(Context context);
}
ConcreteState具體狀態
ConcreteState 具體狀態,每一個子類別實現一個與Context的一個狀態相關的行為。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
//具體狀態A
class ConcreteStateA: State {
public override void Handle(Context context) {
//設定ConcreteStateA的下一個狀態是ConcreteStateB
context.State = new ConcreteStateB();
}
}
//具體狀態B
class ConcreteStateB: State {
public override void Handle(Context context) {
//設定ConcreteStateB的下一個狀態是ConcreteStateA
context.State = new ConcreteStateA();
}
}
Context類別
Context 維護一個 ConcreteState子類別的實例,這個實例定義現在的狀態。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Context {
//狀態屬性
private State state;
Public State State {
get { return state; }
set {
state = value;
Console.WriteLine($"當前狀態: {state.GetType}");
}
}
//定義Context初始狀態
public Context(State state) {
this.state = state
}
//對請求做處理,並設定下一個狀態
public void Request() {
state.Handle(this);
}
}
用戶端
1
2
3
4
5
6
7
//設定Context初始狀態為ConcreteStateA
Context c = new Context(new ConcreteStateA());
//不斷的請求,同時更改狀態
c.Request();
c.Request();
c.Request();
c.Request();
好處與用處
狀態模式的好處是:將與特定狀態相關的行為局部化,並且將不同狀態的行為分割開來。
就是將特定的狀態相關的行為都放入一個物件中,由於所有與狀態相關的程式碼都存在於某個ConcreteState中,所以透過定義新的子類別,可以很容易地增加新的狀態和轉換。
這樣做的目的就是為了消除龐大的條件分支敘述。狀態模式透過各種狀態轉移邏輯分佈到State的子類別之間,來減少相互間的依賴。
大的分支判斷會使得它們難以修改和擴展,任何改動和變化都是致命的。
使用時機
當一個物件的行為取決於它的狀態,而且必須在執行時刻根據狀態改變它的行為時,就可以考慮使用狀態模式了。
另外,如果某項業務有多個狀態的需求時,通常都是一些枚舉常數,狀態的變化都是依靠大量的多分支判斷敘述來實現,此時,應該考慮將每一種業務狀態定義為一個State的子類別。
這麼一來,這些物件就可以不依賴於其他物件而獨立變化了,某一天客戶需要更改需求,增加或減少業務狀態或改變狀態流程,都不困難了。
工作狀態-函數版 v1.0
無物件導向觀念
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int Hour = 0;//鐘點
bool WorkFinished = false; //任務完成指標
public static void Work() {
if(Hour < 12) {
Console.WriteLine($"當前時間:{Hour}點 上午工作,精神百倍");
} else if(Hour < 13) {
Console.WriteLine($"當前時間:{Hour}點 餓了,午飯; 覺得睏,午休");
} else if(Hour < 17) {
Console.WriteLine($"當前時間:{Hour}點 下午狀態還不錯,繼續努力");
} else { //Hour >= 17
if(WorkFinished) {
Console.WriteLine($"當前時間:{Hour}點 下班");
} else {
if(Hour < 21) {
Console.WriteLine($"當前時間:{Hour}點 加班哦,疲累至極");
} else {
Console.WriteLine($"當前時間:{Hour}點 不行了,睡著了");
}
}
}
}
工作狀態-分類版 v2.0
應該要有對外屬性。
但,判斷分支太多,違背「單一職責原則」和「開放-封閉原則」,一旦更改需求,就得對整個方法做改動的,維護出錯的風險很大。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Work {
public int Hour { get; set; }
public bool TaskFinish { get; set; }
public void Coding() {
if(Hour < 12) {
Console.WriteLine($"當前時間:{Hour}點 上午工作,精神百倍");
} else if(Hour < 13) {
Console.WriteLine($"當前時間:{Hour}點 餓了,午飯; 覺得睏,午休");
} else if(Hour < 17) {
Console.WriteLine($"當前時間:{Hour}點 下午狀態還不錯,繼續努力");
} else { //Hour >= 17
if(TaskFinish) {
Console.WriteLine($"當前時間:{Hour}點 下班");
} else {
if(Hour < 21) {
Console.WriteLine($"當前時間:{Hour}點 加班哦,疲累至極");
} else {
Console.WriteLine($"當前時間:{Hour}點 不行了,睡著了");
}
}
}
}
}
方法過長是壞味道(LongMethod)
- 方法過長
- 方法裡面太多的判斷
物件導向設計其實就是希望做到程式碼的責任分解。
判斷分支太多,違背「單一職責原則」,使得任何需求的改動或增加,都需要去更改這個方法了。
也違背了「開放-封閉原則」,比如,老闆覺得加班有點過分,對公司的辦公室管理以及員工的安全都不利,於是發出一個通知,不管任務再多,員工必須在20點之前離開公司。要滿足需求就得更改這個方法,真正要更改的地方只涉及17-22點之間的狀態,但目前的程式碼卻是對整個方法做改動的,維護出錯的風險很大。
怎麼做?
把這些分支想辦法變成一個又一個的類別,增加時不會影響其他類別,然後狀態的變化在各自的類別中完成。
工作狀態-狀態模式版 v3.0
結構
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
工作
+ 鐘點: int
+ 任務完成: bool
+ 設定狀態(in state: 狀態)
+ 寫程式(in work: 工作)
狀態
+ 寫程式(in work: 工作)
上午工作狀態
+ 寫程式(in work: 工作)
中午工作狀態
+ 寫程式(in work: 工作)
下午工作狀態
+ 寫程式(in work: 工作)
傍晚工作狀態
+ 寫程式(in work: 工作)
下班狀態
+ 寫程式(in work: 工作)
睡眠工作狀態
+ 寫程式(in work: 工作)
程式碼
抽象狀態類別
抽象狀態類別,定義一個抽象方法「寫程式」。
1
2
3
4
//抽象狀態類別,定義一個抽象方法「寫程式」
public abstract class State {
public abstract void Coding(Work w);
}
上午/中午工作狀態類別
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//上午工作狀態
public class ForenoonState: State {
public override void Coding(Work w) {
if(w.Hour < 12) {
Console.WriteLine($"當前時間:{w.Hour}點 上午工作,精神百倍");
} else {
//超過12點,則轉入中午工作狀態
w.CurrentState = new NoonState();
w.Coding();
}
}
}
//中午工作狀態
public class NoonState: State {
public override void Coding(Work w) {
if(w.Hour < 13) {
Console.WriteLine($"當前時間:{w.Hour}點 餓了,午飯; 覺得睏,午休");
} else {
//超過13點,則轉入下午工作狀態
w.CurrentState = new AfternoonState();
w.Coding();
}
}
}
下午/晚上工作狀態類別
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//下午工作狀態
public class AfternoonState: State {
public override void Coding(Work w) {
if(w.Hour < 17) {
Console.WriteLine($"當前時間:{w.Hour}點 下午狀態還不錯,繼續努力");
} else {
//超過17點,則轉入晚上工作狀態
w.CurrentState = new EveningState();
w.Coding();
}
}
}
//晚上工作狀態
public class EveningState: State {
public override void Coding(Work w) {
//任務完成
if(w.TaskFinish) {
//任務完成,轉入下班狀態
w.CurrentState = new RestState();
w.Coding();
return;
}
//加班
if(w.Hour < 21) {
Console.WriteLine($"當前時間:{w.Hour}點 加班哦,疲累至極");
} else {
//超過21點,則轉入睡眠工作狀態
w.CurrentState = new SleepingState();
w.Coding();
}
}
}
睡眠狀態/下班休息狀態類別
1
2
3
4
5
6
7
8
9
10
11
12
13
//睡眠狀態
public class SleepingState: State {
public override void Coding(Work w) {
Console.WriteLine($"當前時間:{w.Hour}點 不行了,睡著了");
}
}
//下班休息狀態類別
public class RestState: State {
public override void Coding(Work w) {
Console.WriteLine($"當前時間:{w.Hour}點 下班");
}
}
工作類別
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//工作類別,此時沒有過長的分支判斷敘述
public class Work {
//鐘點屬性
public int Hour { get; set; }
//任務完成屬性
public bool TaskFinish { get; set;}
//當前狀態
public State CurrentState { get; set;}
public Work() {
//工作初始化為:上午狀態,即上午9點開始上班
CurrentState = new ForenoonState();
}
//寫程式
public void Coding() {
CurrentState.Coding(this);
}
}
用戶端程式碼
用戶端程式碼,沒有任何變動,但程式更加靈活了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public static void Main()
{
Work emergencyProject = new Work();
emergencyProject.Hour = 9;
emergencyProject.Coding();
emergencyProject.Hour = 10;
emergencyProject.Coding();
emergencyProject.Hour = 12;
emergencyProject.Coding();
emergencyProject.Hour = 13;
emergencyProject.Coding();
emergencyProject.Hour = 14;
emergencyProject.Coding();
emergencyProject.Hour = 17;
//emergencyProject.TaskFinish = true;
emergencyProject.TaskFinish = false;
emergencyProject.Coding();
emergencyProject.Hour = 19;
emergencyProject.Coding();
emergencyProject.Hour = 22;
emergencyProject.Coding();
}
/* 執行結果
當前時間:9點 上午工作,精神百倍
當前時間:10點 上午工作,精神百倍
當前時間:12點 餓了,午飯; 覺得睏,午休
當前時間:13點 下午狀態還不錯,繼續努力
當前時間:14點 下午狀態還不錯,繼續努力
當前時間:17點 加班哦,疲累至極
當前時間:19點 加班哦,疲累至極
當前時間:22點 不行了,睡著了
*/
增加需求-強制下班狀態
如果「員工必須在20點前離開公司」,只需要增加一個「強制下班狀態」,並修改一下「晚上工作狀態」類別的判斷。
完整程式碼
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
//抽象狀態類別,定義一個抽象方法「寫程式」
public abstract class State {
public abstract void Coding(Work w);
}
//上午工作狀態
public class ForenoonState: State {
public override void Coding(Work w) {
if(w.Hour < 12) {
Console.WriteLine($"當前時間:{w.Hour}點 上午工作,精神百倍");
} else {
//超過12點,則轉入中午工作狀態
w.CurrentState = new NoonState();
w.Coding();
}
}
}
//中午工作狀態
public class NoonState: State {
public override void Coding(Work w) {
if(w.Hour < 13) {
Console.WriteLine($"當前時間:{w.Hour}點 餓了,午飯; 覺得睏,午休");
} else {
//超過13點,則轉入下午工作狀態
w.CurrentState = new AfternoonState();
w.Coding();
}
}
}
//下午工作狀態
public class AfternoonState: State {
public override void Coding(Work w) {
if(w.Hour < 17) {
Console.WriteLine($"當前時間:{w.Hour}點 下午狀態還不錯,繼續努力");
} else {
//超過17點,則轉入晚上工作狀態
w.CurrentState = new EveningState();
w.Coding();
}
}
}
//晚上工作狀態
public class EveningState: State {
public override void Coding(Work w) {
//任務完成
if(w.TaskFinish) {
//任務完成,轉入下班狀態
w.CurrentState = new RestState();
w.Coding();
return;
}
//加班
if(w.Hour < 21) {
Console.WriteLine($"當前時間:{w.Hour}點 加班哦,疲累至極");
} else {
//超過21點,則轉入睡眠工作狀態
w.CurrentState = new SleepingState();
w.Coding();
}
}
}
//睡眠狀態
public class SleepingState: State {
public override void Coding(Work w) {
Console.WriteLine($"當前時間:{w.Hour}點 不行了,睡著了");
}
}
//下班休息狀態類別
public class RestState: State {
public override void Coding(Work w) {
Console.WriteLine($"當前時間:{w.Hour}點 下班");
}
}
//工作類別,此時沒有過長的分支判斷敘述
public class Work {
//鐘點屬性
public int Hour { get; set; }
//任務完成屬性
public bool TaskFinish { get; set;}
//當前狀態
public State CurrentState { get; set;}
public Work() {
//工作初始化為:上午狀態,即上午9點開始上班
CurrentState = new ForenoonState();
}
//寫程式
public void Coding() {
CurrentState.Coding(this);
}
}
public static void Main()
{
Work emergencyProject = new Work();
emergencyProject.Hour = 9;
emergencyProject.Coding();
emergencyProject.Hour = 10;
emergencyProject.Coding();
emergencyProject.Hour = 12;
emergencyProject.Coding();
emergencyProject.Hour = 13;
emergencyProject.Coding();
emergencyProject.Hour = 14;
emergencyProject.Coding();
emergencyProject.Hour = 17;
//emergencyProject.TaskFinish = true;
emergencyProject.TaskFinish = false;
emergencyProject.Coding();
emergencyProject.Hour = 19;
emergencyProject.Coding();
emergencyProject.Hour = 22;
emergencyProject.Coding();
}
/* 執行結果
當前時間:9點 上午工作,精神百倍
當前時間:10點 上午工作,精神百倍
當前時間:12點 餓了,午飯; 覺得睏,午休
當前時間:13點 下午狀態還不錯,繼續努力
當前時間:14點 下午狀態還不錯,繼續努力
當前時間:17點 加班哦,疲累至極
當前時間:19點 加班哦,疲累至極
當前時間:22點 不行了,睡著了
*/