公告
如果您是第一次閱讀我的設(shè)計(jì)模式系列文章蜒灰,建議先閱讀設(shè)計(jì)模式開篇弦蹂,希望能得到您寶貴的建議。
前言
隨著上文 裝飾器模式 提到店鋪老板Bob
改良了機(jī)器人的選型過程强窖,價(jià)格又比你的店鋪實(shí)惠銷量得到了顯著增長凸椿。因此,你決定要加大促銷力度翅溺,再一番折扣刺激過后脑漫。煩人的Alice
又出現(xiàn)了髓抑,這次又會(huì)提出怎樣的需求呢?
正文
某天下午Alice
跑到了你的門店优幸,抱怨他購買的機(jī)器人 “播放歌曲的功能失靈了”吨拍。再也沒法聽到機(jī)器人曼妙的歌聲。
作為一個(gè)“有良心”的賣家网杆,當(dāng)然不能把產(chǎn)品沒做好的事情抖出去羹饰。于是你扛起正義的大旗,保證幫Alice
把機(jī)器人修好碳却。
程序員視角
現(xiàn)在我們希望實(shí)現(xiàn)這樣一個(gè)功能 — — “播放音樂”队秩。我們告訴機(jī)器人對(duì)應(yīng)的指令,機(jī)器人就會(huì)默默的為我們查詢歌曲并播放(唱出來)昼浦。
如何實(shí)現(xiàn)
羅列下業(yè)務(wù)事件:
搜索歌曲刹碾、下載歌曲、播放歌曲座柱、暫停歌曲等這些功能都很好實(shí)現(xiàn),但是其狀態(tài)轉(zhuǎn)化卻是相對(duì)比較復(fù)雜的物舒。
比如給機(jī)器人發(fā)口令“播放周杰倫的稻香”:
1色洞、機(jī)器人會(huì)先搜索稻香、周杰倫關(guān)鍵字冠胯。
2火诸、搜到成功后下載到本機(jī)。
3荠察、然后在執(zhí)行播放按鈕置蜀。
但這只是眾多情況中的一種成功情況,還有很多的異常分支需要把控悉盆。
狀態(tài)模式:分離狀態(tài)的行為盯荤,構(gòu)建狀態(tài)轉(zhuǎn)移方程的同時(shí)不用陷入到實(shí)現(xiàn)細(xì)節(jié)中。
這個(gè)例子中狀態(tài)分為幾種:
1焕盟、外部驅(qū)動(dòng) — — 發(fā)送口令播放
2秋秤、內(nèi)部驅(qū)動(dòng) — — 播放口令后的一系列查詢,下載脚翘,播放行為
如果適用的場景全部是外部驅(qū)動(dòng)
灼卢,則策略模式
與命令模式
也可以適用。
如果適用的場景全部是內(nèi)部驅(qū)動(dòng)
来农,則可構(gòu)建有限狀態(tài)機(jī)
鞋真。
``
代碼實(shí)現(xiàn)
狀態(tài)的上層接口
public interface IMusicState {
// 執(zhí)行音樂相關(guān)操作
IMusicState handle(ContextState contextState, Music music);
// 狀態(tài)是內(nèi)部驅(qū)動(dòng)還是外部驅(qū)動(dòng)
boolean isDependOnUserAction();
}
狀態(tài)管理器(狀態(tài)客戶端)
public class ContextState {
private IMusicState state;
public IMusicState getState() {
return state;
}
public void setState(IMusicState state) {
this.state = state;
}
public synchronized void loop(Music music) {
while (true) {
if (this.state == null) {
this.state = new PendingStateImpl();
}
this.state = this.state.handle(this, music);
if (this.state.isDependOnUserAction()) {
System.out.println("等待用戶再次觸發(fā)狀態(tài)的改變.");
break;
}
}
}
}
狀態(tài)類
播放狀態(tài) —— 此處僅舉出一例,其余狀態(tài)結(jié)構(gòu)相似
public class PlayStateImpl extends MusicState {
public PlayStateImpl() {
System.out.println("構(gòu)建 PlayStateImpl");
}
@Override
public IMusicState handle(ContextState contextState, Music music) {
if (music.url == null) {
return new SearchStateImpl();
}
if (!music.isDownload) {
return new DownloadStateImpl();
}
return new PauseStateImpl();
}
@Override
public boolean isDependOnUserAction() {
return false;
}
}
執(zhí)行結(jié)果
構(gòu)建 PendingStateImpl
構(gòu)建 PlayStateImpl
構(gòu)建 PauseStateImpl
等待用戶再次觸發(fā)狀態(tài)的改變.
總結(jié)
- 在很多情況下沃于,一個(gè)對(duì)象的行為取決于一個(gè)或多個(gè)動(dòng)態(tài)變化的屬性涩咖,這樣的屬性叫做狀態(tài)海诲,這樣的對(duì)象叫做有狀態(tài)的(stateful)對(duì)象,這樣的對(duì)象狀態(tài)是從事先定義好的一系列值中取出的抠藕。當(dāng)一個(gè)這樣的對(duì)象與外部事件產(chǎn)生互動(dòng)時(shí)饿肺,其內(nèi)部狀態(tài)就會(huì)改變,從而使得系統(tǒng)的行為也隨之發(fā)生變化盾似。
- 在UML中可以使用狀態(tài)圖來描述對(duì)象狀態(tài)的變化敬辣。
**狀態(tài)態(tài)模式(State Pattern) **:允許一個(gè)對(duì)象在其內(nèi)部狀態(tài)改變時(shí)改變它的行為,對(duì)象看起來似乎修改了它的類零院。其別名為狀態(tài)對(duì)象(Objects for States)溉跃,狀態(tài)模式是一種對(duì)象行為型模式。
- 狀態(tài)模式描述了對(duì)象狀態(tài)的變化以及對(duì)象如何在每一種狀態(tài)下表現(xiàn)出不同的行為告抄。
- 狀態(tài)模式的關(guān)鍵是引入了一個(gè)抽象類來專門表示對(duì)象的狀態(tài)撰茎,這個(gè)類我們叫做抽象狀態(tài)類,而對(duì)象的每一種具體狀態(tài)類都繼承了該類打洼,并在不同具體狀態(tài)類中實(shí)現(xiàn)了不同狀態(tài)的行為龄糊,包括各種狀態(tài)之間的轉(zhuǎn)換。
在狀態(tài)模式結(jié)構(gòu)中需要理解環(huán)境類與抽象狀態(tài)類的作用:
- 環(huán)境類實(shí)際上就是擁有狀態(tài)的對(duì)象募疮,環(huán)境類有時(shí)候可以充當(dāng)狀態(tài)管理器(State Manager)的角色炫惩,可以在環(huán)境類中對(duì)狀態(tài)進(jìn)行切換操作。
- 抽象狀態(tài)類可以是抽象類阿浓,也可以是接口他嚷,不同狀態(tài)類就是繼承這個(gè)父類的不同子類,狀態(tài)類的產(chǎn)生是由于環(huán)境類存在多個(gè)狀態(tài)芭毙,同時(shí)還滿足兩個(gè)條件:
這些狀態(tài)經(jīng)常需要切換筋蓖,在不同的狀態(tài)下對(duì)象的行為不同。因此可以將不同對(duì)象下的行為單獨(dú)提取出來封裝在具體的狀態(tài)類中退敦,使得環(huán)境類對(duì)象在其內(nèi)部狀態(tài)改變時(shí)可以改變它的行為粘咖,對(duì)象看起來似乎修改了它的類,而實(shí)際上是由于切換到不同的具體狀態(tài)類實(shí)現(xiàn)的苛聘。由于環(huán)境類可以設(shè)置為任一具體狀態(tài)類涂炎,因此它針對(duì)抽象狀態(tài)類進(jìn)行編程,在程序運(yùn)行時(shí)可以將任一具體狀態(tài)類的對(duì)象設(shè)置到環(huán)境類中设哗,從而使得環(huán)境類可以改變內(nèi)部狀態(tài)唱捣,并且改變行為。