本次lz給各位介紹狀態模式,之前在寫設計模式的時候,引入了一些小故事,二十章職責連模式是故事版的最後一篇,之後還剩餘四個設計模式,lz會依照原生的方式去解釋這幾個設計模式,特別是原型模式和直譯器模式,會包含一些其它的內容。
好了,接下來,我們先來看看狀態模式的定義吧。
定義:(源於design pattern):當乙個物件的內在狀態改變時允許改變其行為,這個物件看起來像是改變了其類。
這正是應驗了我們那句話,有些人一旦發生過什麼事以後,就像變了個人似的,這句話其實與狀態模式有異曲同工之妙。
我們仔細體會一下定義當中的要點。
1、有乙個物件,它是有狀態的。
2、這個物件在狀態不同的時候,行為不一樣。
3、這些狀態是可以切換的,而非毫無關係。
前兩點比較好理解,第3點有時候容易給人比較迷惑的感覺,什麼叫這些狀態是可以切換的,而非毫無關係?
舉個例子,比如乙個人的狀態,可以有很多,像生病和健康,這是兩個狀態,這是有關係並且可以轉換的兩個狀態。再比如,睡覺、上班、休息,這也算是一組狀態,這三個狀態也是有關係的並且可以互相轉換。
那如果把生病和休息這兩個狀態放在一起,就顯得毫無意義了。所以這些狀態應該是一組相關並且可互相切換的狀態。
下面我們來看看狀態模式的類圖。
類圖中包含三個角色。
state:狀態介面,它定義了每乙個狀態的行為集合,這些行為會在context中得以使用。
如果針對剛才對於人的狀態的例子來分析,那麼人(person)就是context,狀態介面依然是狀態介面,而具體的狀態類,則可以是睡覺,上班,休息,這一系列狀態。
我們來試著寫乙個dota的例子,最近貌似跟dota幹上了,不為其他,就因為dota伴隨了lz四年的大學時光。
玩過的朋友都知道,dota裡的英雄有很多狀態,比如正常,眩暈,加速,減速等等。相信就算沒有玩過dota的朋友們,在其它遊戲裡也能見到類似的情況。那麼假設我們的dota沒有使用狀態模式,則我們的英雄類會非常複雜和難以維護,我們來看下,原始版的英雄類是怎樣的。
複製**
package com.state;
//英雄類
public class hero
//停止跑動
public void stoprun()
//開始跑動
public void startrun()
final hero hero = this;
runthread = new thread(new runnable() catch (interruptedexception e) }}
});system.out.println("--------------開始跑動---------------");
runthread.start();
}private boolean isrunning()
//英雄類開始奔跑
private void run() throws interruptedexceptionelse if (state == speed_down) else if (state == swim) else
}複製**
下面我們寫乙個客戶端類,去試圖讓英雄在各種狀態下奔跑一下。
複製**
package com.state;
public class main 複製**
可以看到,我們的英雄在跑動過程中隨著狀態的改變,會以不同的狀態進行跑動。
狀態模式解決的問題:狀態模式主要解決的是當控制乙個物件狀態的條件表示式過於複雜時的情況。把狀態的判斷邏輯轉移到表示不同狀態的一系列類中,可以把複雜的判斷邏輯簡化。
不用說,狀態模式是可以解決我們上面的if else結構的,我們採用狀態模式,利用多型的特性可以消除掉if else結構。這樣所帶來的好處就是可以大大的增加程式的可維護性與擴充套件性。
下面我們就使用狀態模式對上面的例子進行改善,首先第一步,就是我們需要定義乙個狀態介面,這個介面就只有乙個方法,就是run。
複製**
package com.state;
public inte***ce runstate 複製**
複製**
package com.state;
public class speedupstate implements runstate catch (interruptedexception e) {}
hero.setstate(hero.common);
system.out.println("------加速狀態結束,變為正常狀態------");
}複製**
複製**
package com.state;
public class speeddownstate implements runstate catch (interruptedexception e) {}
hero.setstate(hero.common);
system.out.println("------減速狀態結束,變為正常狀態------");
}複製**
複製**
package com.state;
public class swimstate implements runstate catch (interruptedexception e) {}
hero.setstate(hero.common);
system.out.println("------眩暈狀態結束,變為正常狀態------");
}複製**
這下我們的英雄類也要相應的改動一下,最主要的改動就是那些if else可以刪掉了,如下。
複製**
package com.state;
//英雄類
public class hero
//停止跑動
public void stoprun()
//開始跑動
public void startrun()
final hero hero = this;
runthread = new thread(new runnable()
}});
system.out.println("--------------開始跑動---------------");
runthread.start();
}private boolean isrunning()複製**
可以看到,現在我們的英雄類優雅了許多,我們使用剛才同樣的客戶端執行即可得到同樣的結果。
對比我們的原始例子,現在我們使用狀態模式之後,有幾個明顯的優點:
一、我們去掉了if else結構,使得**的可維護性更強,不易出錯,這個優點挺明顯,如果試圖讓你更改跑動的方法,是剛才的一堆if else好改,還是分成了若干個具體的狀態類好改呢?答案是顯而易見的。
二、使用多型代替了條件判斷,這樣我們**的擴充套件性更強,比如要增加一些狀態,假設有加速20%,加速10%,減速10%等等等(這並不是虛構,dota當中是真實存在這些狀態的),會非常的容易。
三、狀態是可以被共享的,這個在上面的例子當中有體現,看下hero類當中的四個static final變數就知道了,因為狀態類一般是沒有自己的內部狀態的,所有它只是乙個具有行為的物件,因此是可以被共享的。
四、狀態的轉換更加簡單安全,簡單體現在狀態的分割,因為我們把一堆if else分割成了若干個**段分別放在幾個具體的狀態類當中,所以轉換起來當然更簡單,而且每次轉換的時候我們只需要關注乙個固定的狀態到其他狀態的轉換。安全體現在型別安全,我們設定上下文的狀態時,必須是狀態介面的實現類,而不是原本的乙個整數,這可以杜絕魔數以及不正確的狀態碼。
狀態模式適用於某乙個物件的行為取決於該物件的狀態,並且該物件的狀態會在執行時轉換,又或者有很多的if else判斷,而這些判斷只是因為狀態不同而不斷的切換行為。
上面的適用場景是很多狀態模式的介紹中都提到的,下面我們就來看下剛才dota中,英雄例子的類圖。
可以看到,這個類圖與狀態模式的標準類圖是幾乎一模一樣的,只是多了一條狀態介面到上下文的依賴線,而這個是根據實際需要新增的,而且一般情況下都是需要的。
狀態模式也有它的缺點,不過它的缺點和大多數模式相似,有兩點。
1、會增加的類的數量。
2、使系統的複雜性增加。
儘管狀態模式有著這樣的缺點,但是往往我們犧牲複雜性去換取的高可維護性和擴充套件性是相當值得的,除非增加了複雜性以後,對於後者的提公升會乎其微。
狀態模式在專案當中也算是較經常會碰到的乙個設計模式,但是通常情況下,我們還是在看到if else的情況下,對專案進行重構時使用,又或者你十分確定要做的專案會朝著狀態模式發展,一般情況下,還是不建議在專案的初期使用。
原文出處:
設計模式(二十一) 狀態模式
狀態模式也是一種行為型模式,當我們的程式中需要一些狀態轉換,對於不同的狀態需要不同的行為時,我們就可以考慮使用狀態模式。下面用交通燈來當例子。我們需要紅黃綠三種顏色的狀態。inte ce state class redstate implements state class yellowstate ...
設計模式(二十一) 狀態模式
狀態模式也是一種行為型模式,當我們的程式中需要一些狀態轉換,對於不同的狀態需要不同的行為時,我們就可以考慮使用狀態模式。下面用交通燈來當例子。我們需要紅黃綠三種顏色的狀態。inte ce state class redstate implements state class yellowstate ...
python設計模式(二十一) 狀態模式
狀態模式,當物件的內部狀態改變的時候,允許物件執行不同的流程,看起來就像改寫了乙個物件,核心的方法是把複雜狀態變化情況下的流程抽象出來,簡化複雜情況狀態的判斷。我們設計乙個應用場景 當狀態是cpu使用率,在不同狀態下的自動化運維指令碼執行不同的操作 示例code class base def exe...