State 設計模式

Jc
7 min readOct 8, 2023

在好久之前有寫過一篇文章上介紹 Strategy 設計模式的,但那時候還不清楚它與 State 設計模式有什麼不同。

它們其實很像,但根據 Refactoring Guru 的解釋,Strategy 與 State 最大的差別是在與:

Strategy 彼此之間的策略對象是完全獨立,它們並不會知道彼此存在 ,就像是在 Strategy 設計模式 大衛的女朋友們

State 沒有限制這些狀態對象之間的依賴關係

State 設計模式是什麼?

State 設計模式是一種行爲設計模式,只要內部的狀態改變時,就會改變外部的行爲

沒有使用 State 設計模式會怎麼樣嗎? 🤯

這次一樣用個故事來瞭解 State 設計模式的使用方式,這次要講的是人格分裂的湯姆的故事。

湯姆角色設定
湯姆從小發現自己擁有三個人格,分別是:

  • 外向人格
  • 自閉人格
  • 暴力人格

因此不同人格的特質就會顯示在湯姆的這幾個行爲上:

  • 吃飯
  • 跟朋友聚會
  • 睡覺

對了,這三個人格會在湯姆睡著時輪班。

人格彼此間的私怨

  • 「外向人格」討厭「自閉人格」,因此只會把控制權交給「暴力人格」
  • 「自閉人格」也討厭「外向人格」,因此只會把控制權交給「暴力人格」
  • 「暴力人格」跟「外向人格」、「內向人格」都沒有恩怨,所以輪班的時候就會隨機把控制權跟任一個

三種人格行爲
外向人格的行爲:

  • 吃飯:去 Buffet ~ 🍕
  • 跟朋友聚會:跟一大群朋友開心聊天到時間
  • 睡覺:輪班:明天換暴力傾向人格上班了

自閉人格的行爲:

  • 吃飯:我都沒有朋友,吃什麼,餓死算了。。😮‍💨
  • 跟朋友聚會:朋友是什麼?我想空氣就是我最好的朋友。。。
  • 睡覺:輪班:恩。。明天換暴力傾向人格了。。。

暴力人格的行爲:

  • 吃飯:吃飯總是吃到翻桌!🤬
  • 跟朋友聚會:沒跟朋友打起來才叫人意外!
  • 睡覺:隨便選一個啦!

沒有 State 設計模式的程式碼

package main

import (
"fmt"
"math/rand"
"time"
)

const (
OutgoingCharacterType = iota
ASDCharacterType
ViolenceTendencyCharacterType
)

type Tom struct {
character int
}

func (m *Tom) eating() {
// 如果外向人格
if m.character == OutgoingCharacterType {
fmt.Println("外向人格 - 吃飯:去 Buffet ~")
}
// 如果是自閉人格
if m.character == ASDCharacterType {
fmt.Println("自閉人格 - 吃飯:我都沒有朋友,吃什麼,餓死算了。。")
}
// 如果是暴力人格
if m.character == ViolenceTendencyCharacterType {
fmt.Println("暴力傾向人格 - 吃飯:吃飯總是吃到翻桌!")
}
}

func (m *Tom) gatherWithFriends() {
// 如果外向人格
if m.character == OutgoingCharacterType {
fmt.Println("外向人格 - 跟朋友相聚:跟一大群朋友開心聊天到時間")
}
// 如果是自閉人格
if m.character == ASDCharacterType {
fmt.Println("自閉人格 - 跟朋友相聚:朋友是什麼?我想空氣就是我最好的朋友。。。")
}
// 如果是暴力人格
if m.character == ViolenceTendencyCharacterType {
fmt.Println("暴力傾向人格 - 跟朋友相聚:沒跟朋友打起來才叫人意外!")
}
}

func (m *Tom) sleeping() {
// 如果外向人格
if m.character == OutgoingCharacterType {
fmt.Println("外向人格 - 輪班:明天換暴力傾向人格上班了")
m.changedCharacter(ViolenceTendencyCharacterType)
return
}
// 如果是自閉人格
if m.character == ASDCharacterType {
fmt.Println("自閉人格 - 輪班:恩。。明天換暴力傾向人格了。。。")
m.changedCharacter(ViolenceTendencyCharacterType)
return
}
// 如果是暴力人格
if m.character == ViolenceTendencyCharacterType {
// 隨便選出下一個
characters := []int{OutgoingCharacterType, ASDCharacterType}
rand.Seed(time.Now().UnixNano())
i := rand.Intn(2)
next := characters[i]
if next == OutgoingCharacterType {
fmt.Println("暴力傾向人格 - 輪班:明天居然是外向人格,嘖嘖")
m.changedCharacter(next)
return
}

if next == ASDCharacterType {
fmt.Println("暴力傾向人格 - 輪班:哼,明天居然是那個懦弱的自閉人格!嘖嘖!")
m.changedCharacter(next)
return
}
}
}

func (m *Tom) changedCharacter(c int) {
m.character = c
}

func main() {
tom := &Tom{character: OutgoingCharacterType}
fmt.Println("第 1 天")
tom.eating()
tom.gatherWithFriends()
tom.sleeping()
fmt.Println("第 2 天")
tom.eating()
tom.gatherWithFriends()
tom.sleeping()
fmt.Println("第 3 天")
tom.eating()
tom.gatherWithFriends()
tom.sleeping()
fmt.Println("第 4 天")
tom.eating()
tom.gatherWithFriends()
tom.sleeping()
fmt.Println("第 5 天")
tom.eating()
tom.gatherWithFriends()
tom.sleeping()
}

第 1 天
外向人格 - 吃飯:去 Buffet ~
外向人格 - 跟朋友相聚:跟一大群朋友開心聊天到時間
外向人格 - 輪班:明天換暴力傾向人格上班了
第 2 天
暴力傾向人格 - 吃飯:吃飯總是吃到翻桌!
暴力傾向人格 - 跟朋友相聚:沒跟朋友打起來才叫人意外!
暴力傾向人格 - 輪班:明天居然是外向人格,嘖嘖
第 3 天
外向人格 - 吃飯:去 Buffet ~
外向人格 - 跟朋友相聚:跟一大群朋友開心聊天到時間
外向人格 - 輪班:明天換暴力傾向人格上班了
第 4 天
暴力傾向人格 - 吃飯:吃飯總是吃到翻桌!
暴力傾向人格 - 跟朋友相聚:沒跟朋友打起來才叫人意外!
暴力傾向人格 - 輪班:哼,明天居然是那個懦弱的自閉人格!嘖嘖!
第 5 天
自閉人格 - 吃飯:我都沒有朋友,吃什麼,餓死算了。。
自閉人格 - 跟朋友相聚:朋友是什麼?我想空氣就是我最好的朋友。。。
自閉人格 - 輪班:恩。。明天換暴力傾向人格了。。。
暴力傾向人格 - 輪班:明天居然是外向人格,嘖嘖

問題:判斷式因爲不同的狀態而增加 🥲

上面的問題跟 Strategy 設計模式 差不多,都會因爲狀態而增加。

用這個例子來看,湯姆目前只有分裂成三個人格(狀態),每個行爲的內容又很單純,因此看起來好像還可以接受;然而如果隨著人格(狀態)的增加與行爲變得複雜,那就會很難以維護了 🤯

因此我們立即前往 圈圈工程師 看看如何使用 State 設計模式來改善上述的問題吧~

--

--