安卓開發(fā)中狀態(tài)模式的應(yīng)用
一、狀態(tài)模式的基本概念
狀態(tài)模式(State Pattern)
贡必,又稱狀態(tài)對(duì)象模式(State Object Pattern),是一種行為型設(shè)計(jì)模式庸毫,它允許一個(gè)對(duì)象在其內(nèi)部狀態(tài)改變時(shí)改變其行為仔拟。這個(gè)對(duì)象看上去就像是改變了它的類一樣
狀態(tài)模式有以下三個(gè)角色:
-
Context
(環(huán)境):定義了客戶端需要的接口,并維護(hù)一個(gè)State的實(shí)例飒赃,將與狀態(tài)相關(guān)的操作委托給當(dāng)前的ConcreteState對(duì)象來處理利花。 -
State
(抽象狀態(tài)):定義一個(gè)接口,以封裝使用Context的一個(gè)特定狀態(tài)相關(guān)的行為载佳。 -
ConcreteState
(具體狀態(tài)):實(shí)現(xiàn)抽象狀態(tài)定義的接口炒事。
狀態(tài)模式的UML圖如下:
二、安卓源碼中的狀態(tài)模式實(shí)例
安卓源碼中有很多使用狀態(tài)模式的例子蔫慧,例如WifiManager挠乳、AudioManager、TelephonyManager等。這里我們以WifiManager為例睡扬,來分析它是如何使用狀態(tài)模式來管理Wifi連接和斷開的過程盟蚣。
WifiManager是一個(gè)系統(tǒng)服務(wù),它提供了管理Wifi連接和掃描附近熱點(diǎn)的功能卖怜。WifiManager內(nèi)部有一個(gè)名為WifiStateMachine的類屎开,它是一個(gè)有限狀態(tài)機(jī)(Finite State Machine),用來控制Wifi連接和斷開的邏輯马靠。WifiStateMachine繼承自StateMachine類奄抽,StateMachine類是一個(gè)通用的有限狀態(tài)機(jī)框架,它提供了創(chuàng)建和切換狀態(tài)的方法甩鳄。
WifiStateMachine定義了以下幾個(gè)內(nèi)部類逞度,分別對(duì)應(yīng)不同的Wifi連接狀態(tài):
- DefaultState:默認(rèn)狀態(tài),所有消息都會(huì)先經(jīng)過這個(gè)狀態(tài)處理妙啃。
- InitialState:初始狀態(tài)第晰,當(dāng)WifiStateMachine創(chuàng)建時(shí)進(jìn)入這個(gè)狀態(tài)。
- SupplicantStartingState:Supplicant啟動(dòng)狀態(tài)彬祖,當(dāng)打開Wifi時(shí)進(jìn)入這個(gè)狀態(tài)。
-
SupplicantStartedState
:Supplicant已啟動(dòng)狀態(tài)品抽,當(dāng)Supplicant啟動(dòng)成功時(shí)進(jìn)入這個(gè)狀態(tài)储笑。 - DriverStartingState:驅(qū)動(dòng)啟動(dòng)狀態(tài),當(dāng)開始掃描熱點(diǎn)時(shí)進(jìn)入這個(gè)狀態(tài)圆恤。
-
DriverStartedState
:驅(qū)動(dòng)已啟動(dòng)狀態(tài)突倍,當(dāng)驅(qū)動(dòng)啟動(dòng)成功時(shí)進(jìn)入這個(gè)狀態(tài)。 -
ScanModeState
:掃描模式狀態(tài)盆昙,當(dāng)處于掃描熱點(diǎn)的過程中時(shí)進(jìn)入這個(gè)狀態(tài)羽历。 -
ConnectModeState
:連接模式狀態(tài),當(dāng)準(zhǔn)備連接某個(gè)熱點(diǎn)時(shí)進(jìn)入這個(gè)狀態(tài)淡喜。 - L2ConnectedState:L2層已連接狀態(tài)秕磷,當(dāng)與某個(gè)熱點(diǎn)建立L2層連接時(shí)進(jìn)入這個(gè)狀態(tài)。
- ObtainingIpState:獲取IP地址狀態(tài)炼团,當(dāng)從某個(gè)熱點(diǎn)獲取IP地址時(shí)進(jìn)入這個(gè)狀態(tài)澎嚣。
- VerifyingLinkState:驗(yàn)證鏈接狀態(tài),當(dāng)驗(yàn)證IP地址是否有效時(shí)進(jìn)入這個(gè)狀態(tài)瘟芝。
- CaptivePortalCheckState:檢測(cè)門戶網(wǎng)站(Captive Portal)狀態(tài)易桃,當(dāng)檢測(cè)是否需要登錄門戶網(wǎng)站時(shí)進(jìn)入這個(gè)狀態(tài)。
-
ConnectedState
:已連接狀態(tài)锌俱,當(dāng)成功連接某個(gè)熱點(diǎn)并獲取網(wǎng)絡(luò)訪問權(quán)限時(shí)進(jìn)入這個(gè)狀態(tài)晤郑。 - RoamingState:漫游(Roaming)狀態(tài),當(dāng)從一個(gè)熱點(diǎn)切換到另一個(gè)熱點(diǎn)時(shí)進(jìn)入這個(gè)狀態(tài)。
- DisconnectingState:斷開連接中狀態(tài)造寝,當(dāng)主動(dòng)或被動(dòng)斷開某個(gè)熱點(diǎn)時(shí)進(jìn)入這個(gè)
狀態(tài)模式的優(yōu)點(diǎn)是:
- 封裝了狀態(tài)轉(zhuǎn)換規(guī)則磕洪,使?fàn)顟B(tài)轉(zhuǎn)換更加清晰和安全。
- 將所有與某個(gè)狀態(tài)有關(guān)的行為放到一個(gè)類中匹舞,方便增加新的狀態(tài)和修改狀態(tài)行為褐鸥。
- 避免使用大量的條件判斷語句,提高代碼的可讀性和可維護(hù)性赐稽。
狀態(tài)模式的缺點(diǎn)是:
- 增加了系統(tǒng)中類和對(duì)象的個(gè)數(shù)叫榕,增加了系統(tǒng)的復(fù)雜度。
- 對(duì)開閉原則的支持不太好姊舵,如果要增加新的狀態(tài)或者修改狀態(tài)轉(zhuǎn)換條件晰绎,需要修改源代碼。
2.1 Kotlin使用密封類實(shí)現(xiàn)
密封類或密封接口可以限制類的繼承結(jié)構(gòu)括丁,保證類型安全和可維護(hù)性荞下。可以用來實(shí)現(xiàn)狀態(tài)模式史飞,因?yàn)樗鼈兛梢员硎疽粋€(gè)有限的狀態(tài)集合尖昏,而且可以使用when表達(dá)式來匹配不同的狀態(tài),并執(zhí)行相應(yīng)的操作构资。
下面是一個(gè)使用Kotlin密封類實(shí)現(xiàn)狀態(tài)模式的例子抽诉,它模擬了一個(gè)電視機(jī)和遙控器,電視機(jī)有三種狀態(tài):開機(jī)吐绵、關(guān)機(jī)和待機(jī)迹淌,每種狀態(tài)下可以執(zhí)行不同的操作。
// 密封類
sealed class TVState {
// 開機(jī)狀態(tài)
object PowerOn : TVState() {
fun show() {
println("電視機(jī)已開機(jī)己单,顯示畫面")
}
}
// 關(guān)機(jī)狀態(tài)
object PowerOff : TVState() {
fun hide() {
println("電視機(jī)已關(guān)機(jī)唉窃,隱藏畫面")
}
}
// 待機(jī)狀態(tài)
object Standby : TVState() {
fun dim() {
println("電視機(jī)已待機(jī),畫面變暗")
}
}
}
// 環(huán)境類
class TV(var state: TVState) { // 維持一個(gè)密封類對(duì)象的引用
fun powerOn() { // 開機(jī)操作
when (state) { // 根據(jù)不同的狀態(tài)執(zhí)行不同的操作
is TVState.PowerOn -> state.show()
is TVState.PowerOff -> {
state = TVState.PowerOn // 切換到開機(jī)狀態(tài)
state.show()
}
is TVState.Standby -> {
state = TVState.PowerOn // 切換到開機(jī)狀態(tài)
state.show()
}
}
}
fun powerOff() { // 關(guān)機(jī)操作
when (state) { // 根據(jù)不同的狀態(tài)執(zhí)行不同的操作
is TVState.PowerOn -> {
state = TVState.PowerOff // 切換到關(guān)機(jī)狀態(tài)
state.hide()
}
is TVState.PowerOff -> state.hide()
is TVState.Standby -> {
state = TVState.PowerOff // 切換到關(guān)機(jī)狀態(tài)
state.hide()
}
}
}
fun standby() { // 待機(jī)操作
when (state) { // 根據(jù)不同的狀態(tài)執(zhí)行不同的操作
is TVState.PowerOn -> {
state = TVState.Standby // 切換到待機(jī)狀態(tài)
state.dim()
}
is TVState.PowerOff -> state.hide()
is TVState.Standby -> state.dim()
}
}
}
// 遙控器類
class RemoteControl(private val tv: TV) { // 持有一個(gè)環(huán)境對(duì)象的引用
fun pressPowerButton() { // 按下電源鍵操作
tv.powerOn() // 調(diào)用環(huán)境對(duì)象的開機(jī)操作
}
fun pressStandbyButton() { // 按下待機(jī)鍵操作
tv.standby() // 調(diào)用環(huán)境對(duì)象的待機(jī)操作
}
fun pressPowerOffButton() { // 按下關(guān)機(jī)鍵操作
tv.powerOff() // 調(diào)用環(huán)境對(duì)象的關(guān)機(jī)操作
}
}
fun main() {
val tv = TV(TVState.PowerOff) // 創(chuàng)建環(huán)境對(duì)象纹笼,初始為關(guān)機(jī)狀態(tài)
val remoteControl = RemoteControl(tv) // 創(chuàng)建遙控器對(duì)象纹份,持有環(huán)境對(duì)象的引用
remoteControl.pressPowerButton() // 按下電源鍵操作
remoteControl.pressStandbyButton() // 按下待機(jī)鍵操作
remoteControl.pressPowerOffButton() // 按下關(guān)機(jī)鍵操作
}
輸出結(jié)果:
電視機(jī)已開機(jī),顯示畫面
電視機(jī)已待機(jī)廷痘,畫面變暗
電視機(jī)已關(guān)機(jī)矮嫉,隱藏畫面
2.2 Kotlin中的狀態(tài)模式加入?yún)f(xié)程
// 定義一個(gè)枚舉類,表示電視機(jī)的狀態(tài)
enum class TVState {
// 開機(jī)狀態(tài)
PowerOn {
override fun handle() {
println("電視機(jī)已開機(jī)牍疏,顯示畫面")
}
},
// 關(guān)機(jī)狀態(tài)
PowerOff {
override fun handle() {
println("電視機(jī)已關(guān)機(jī)蠢笋,隱藏畫面")
}
},
// 待機(jī)狀態(tài)
Standby {
override fun handle() {
println("電視機(jī)已待機(jī),畫面變暗")
}
};
// 定義一個(gè)抽象方法鳞陨,用來執(zhí)行不同狀態(tài)下的操作
abstract fun handle()
}
// 定義一個(gè)協(xié)程上下文元素昨寞,用來保存和恢復(fù)電視機(jī)的狀態(tài)
class TVContextElement(var state: TVState) : AbstractCoroutineContextElement(Key) {
companion object Key : CoroutineContext.Key<TVContextElement>
}
// 定義一個(gè)協(xié)程作用域的擴(kuò)展函數(shù)瞻惋,用來創(chuàng)建一個(gè)帶有電視機(jī)狀態(tài)的協(xié)程
fun CoroutineScope.tvCoroutine(state: TVState, block: suspend CoroutineScope.() -> Unit): Job {
return launch(TVContextElement(state)) {
block()
}
}
// 定義一個(gè)協(xié)程上下文元素的擴(kuò)展函數(shù),用來切換電視機(jī)的狀態(tài)
suspend fun TVContextElement.switchState(newState: TVState) {
state = newState
}
// 定義一個(gè)掛起函數(shù)援岩,用來執(zhí)行電視機(jī)的操作
suspend fun handleTV() {
// 獲取當(dāng)前協(xié)程上下文中的電視機(jī)狀態(tài)元素歼狼,并執(zhí)行對(duì)應(yīng)狀態(tài)下的操作
coroutineContext[TVContextElement.Key]?.state?.handle()
}
// 定義一個(gè)遙控器類,用來控制電視機(jī)的協(xié)程
class RemoteControl(private val tvJob: Job) {
fun pressPowerButton() { // 按下電源鍵操作
GlobalScope.launch { // 在全局作用域中啟動(dòng)一個(gè)新的協(xié)程
tvJob.join() // 等待電視機(jī)協(xié)程完成
tvCoroutine(TVState.PowerOn) { // 創(chuàng)建一個(gè)新的帶有開機(jī)狀態(tài)的電視機(jī)協(xié)程
handleTV() // 執(zhí)行開機(jī)操作
}
}
}
fun pressStandbyButton() { // 按下待機(jī)鍵操作
GlobalScope.launch { // 在全局作用域中啟動(dòng)一個(gè)新的協(xié)程
tvJob.join() // 等待電視機(jī)協(xié)程完成 原創(chuàng)作者:簡書-長點(diǎn)點(diǎn)
tvCoroutine(TVState.Standby) { // 創(chuàng)建一個(gè)新的帶有待機(jī)狀態(tài)的電視機(jī)協(xié)程
handleTV() // 執(zhí)行待機(jī)操作
}
}
}
fun pressPowerOffButton() { // 按下關(guān)機(jī)鍵操作
GlobalScope.launch { // 在全局作用域中啟動(dòng)一個(gè)新的協(xié)程
tvJob.join() // 等待電視機(jī)協(xié)程完成
tvCoroutine(TVState.PowerOff) { // 創(chuàng)建一個(gè)新的帶有關(guān)機(jī)狀態(tài)的電視機(jī)協(xié)程
handleTV() // 執(zhí)行關(guān)機(jī)操作
}
}
}
}
fun main() {
val tvJob = GlobalScope.tvCoroutine(TVState.PowerOff) {} // 創(chuàng)建一個(gè)初始為關(guān)機(jī)狀態(tài)的電視機(jī)協(xié)程
val remoteControl = RemoteControl(tvJob) // 創(chuàng)建遙控器對(duì)象享怀,持有電視機(jī)協(xié)程的引用
remoteControl.pressPowerButton() // 按下電源鍵操作
remoteControl.pressStandbyButton() // 按下待機(jī)鍵操作
remoteControl.pressPowerOffButton() // 按下關(guān)機(jī)鍵操作
GlobalScope.cancel() // 取消全局作用域中的所有協(xié)程
}
電視機(jī)已開機(jī)羽峰,顯示畫面
電視機(jī)已待機(jī),畫面變暗
電視機(jī)已關(guān)機(jī)添瓷,隱藏畫面
這個(gè)例子中梅屉,我使用了Kotlin協(xié)程中的狀態(tài)機(jī)的思想,將電視機(jī)的狀態(tài)保存在一個(gè)協(xié)程上下文元素中鳞贷,然后使用一個(gè)協(xié)程構(gòu)建器來創(chuàng)建一個(gè)帶有電視機(jī)狀態(tài)的協(xié)程坯汤,再使用一個(gè)掛起函數(shù)來切換和執(zhí)行電視機(jī)的狀態(tài)。遙控器類則是用來控制電視機(jī)協(xié)程的啟動(dòng)和取消的搀愧。這樣惰聂,每次按下遙控器的按鈕時(shí),都會(huì)創(chuàng)建一個(gè)新的電視機(jī)協(xié)程咱筛,并根據(jù)不同的狀態(tài)執(zhí)行不同的操作搓幌。
2.3 Java接口實(shí)現(xiàn)
// Java實(shí)現(xiàn)
// 抽象狀態(tài)類
interface LightState {
void handle(Light light); // 處理操作
}
// 開燈狀態(tài)類
class LightOnState implements LightState {
@Override
public void handle(Light light) {
System.out.println("燈已經(jīng)開了");
light.setState(new LightOffState()); // 切換到關(guān)燈狀態(tài)
}
}
// 關(guān)燈狀態(tài)類
class LightOffState implements LightState {
@Override
public void handle(Light light) {
System.out.println("燈已經(jīng)關(guān)了");
light.setState(new LightOnState()); // 切換到開燈狀態(tài)
}
}
// 環(huán)境類
class Light {
private LightState state; // 維持一個(gè)抽象狀態(tài)對(duì)象的引用
public Light(LightState state) { // 構(gòu)造函數(shù)
this.state = state;
}
public void setState(LightState state) { // 設(shè)置新狀態(tài)
this.state = state;
}
public void pressSwitch() { // 按下開關(guān)操作
state.handle(this); // 調(diào)用狀態(tài)方法
}
}
public class Main {
public static void main(String[] args) {
Light light = new Light(new LightOffState()); // 創(chuàng)建環(huán)境對(duì)象,初始為關(guān)燈狀態(tài)
light.pressSwitch(); // 按下開關(guān)操作
light.pressSwitch(); // 按下開關(guān)操作
}
}
輸出結(jié)果:
燈已經(jīng)關(guān)了
燈已經(jīng)開了
2.4 C++實(shí)現(xiàn)
下面是一個(gè)使用C++實(shí)現(xiàn)的狀態(tài)模式的例子迅箩,它模擬了一個(gè)電視機(jī)遙控器溉愁,電視機(jī)有兩種狀態(tài):開機(jī)和關(guān)機(jī),每種狀態(tài)下可以執(zhí)行不同的操作沙热。
// 抽象狀態(tài)類
class TVState {
public:
virtual void handle() = 0; // 處理操作
};
// 開機(jī)狀態(tài)類
class PowerOnState : public TVState {
public:
void handle() {
cout << "電視機(jī)已開機(jī)" << endl;
}
};
// 關(guān)機(jī)狀態(tài)類
class PowerOffState : public TVState {
public:
void handle() {
cout << "電視機(jī)已關(guān)機(jī)" << endl;
}
};
// 環(huán)境類
class TVContext {
private:
TVState* state; // 維持一個(gè)抽象狀態(tài)對(duì)象的引用
public:
TVContext() : state(nullptr) {} // 默認(rèn)構(gòu)造函數(shù)
TVContext(TVState* state) : state(state) {} // 構(gòu)造函數(shù)
~TVContext() { delete state; } // 析構(gòu)函數(shù)
void setState(TVState* state) { // 設(shè)置新狀態(tài)
delete this->state; // 釋放舊狀態(tài)
this->state = state; // 更改狀態(tài)
}
TVState* getState() { return state; } // 獲取當(dāng)前狀態(tài)
void powerOn() { // 開機(jī)操作
setState(new PowerOnState()); // 設(shè)置開機(jī)狀態(tài)
state->handle(); // 調(diào)用狀態(tài)方法
}
void powerOff() { // 關(guān)機(jī)操作
setState(new PowerOffState()); // 設(shè)置關(guān)機(jī)狀態(tài)
state->handle(); // 調(diào)用狀態(tài)方法
}
};
int main() {
TVContext* tv = new TVContext(); // 創(chuàng)建環(huán)境對(duì)象
tv->powerOn(); // 開機(jī)操作
tv->powerOff(); // 關(guān)機(jī)操作
delete tv; // 釋放環(huán)境對(duì)象
return 0;
}
輸出結(jié)果:
電視機(jī)已開機(jī)
電視機(jī)已關(guān)機(jī)
三、與其他相似設(shè)計(jì)模式對(duì)比
- 策略模式
讓對(duì)象可以根據(jù)自身的選擇或配置來選擇不同的算法罢缸,實(shí)現(xiàn)動(dòng)態(tài)地改變對(duì)象的行為篙贸。 - 模板方法模式
讓子類可以在不改變算法結(jié)構(gòu)的情況下重寫某些步驟,實(shí)現(xiàn)代碼的復(fù)用和擴(kuò)展枫疆。 - 命令模式
將一個(gè)請(qǐng)求封裝為一個(gè)對(duì)象爵川,實(shí)現(xiàn)請(qǐng)求和執(zhí)行的解耦和撤銷/重做功能。
設(shè)計(jì)模式 | 目的 | 優(yōu)點(diǎn) | 缺點(diǎn) | 使用場景 |
---|---|---|---|---|
策略模式 | 定義一組可互換的算法息楔,讓對(duì)象可以根據(jù)自身的選擇或配置來選擇不同的算法 | 可以動(dòng)態(tài)地改變對(duì)象的行為寝贡,增加了對(duì)象的靈活性和可擴(kuò)展性,避免了多重條件語句 | 增加了系統(tǒng)中類和對(duì)象的個(gè)數(shù)值依,客戶端必須知道不同的算法和它們之間的區(qū)別 | 當(dāng)一個(gè)對(duì)象有多種行為圃泡,而且這些行為可以相互替換時(shí),例如不同的排序算法愿险、壓縮算法颇蜡、加密算法等 |
模板方法模式 | 定義一個(gè)算法的骨架,讓子類可以在不改變算法結(jié)構(gòu)的情況下重寫某些步驟 | 可以實(shí)現(xiàn)代碼復(fù)用,增加了子類的靈活性和可擴(kuò)展性风秤,遵循了開閉原則 | 增加了系統(tǒng)中類和對(duì)象的個(gè)數(shù)鳖目,可能導(dǎo)致子類過多或過于復(fù)雜,增加了維護(hù)成本 | 當(dāng)一個(gè)算法有固定的步驟缤弦,而且這些步驟中有一些可以由子類來實(shí)現(xiàn)時(shí)领迈,例如不同類型的文檔、游戲碍沐、窗口等 |
命令模式 | 將一個(gè)請(qǐng)求封裝為一個(gè)對(duì)象狸捅,讓不同的請(qǐng)求者或接收者來執(zhí)行這個(gè)請(qǐng)求 | 可以實(shí)現(xiàn)請(qǐng)求和執(zhí)行的解耦,增加了請(qǐng)求者和接收者之間的靈活性和可擴(kuò)展性抢韭,支持撤銷和重做操作 | 增加了系統(tǒng)中類和對(duì)象的個(gè)數(shù)薪贫,可能導(dǎo)致命令過多或過于復(fù)雜,增加了維護(hù)成本 | 當(dāng)需要將請(qǐng)求者和執(zhí)行者分開刻恭,或者需要支持撤銷和重做操作時(shí)瞧省,例如遙控器、撤銷/重做鳍贾、宏命令等 |