會區分事物內部的狀態,事物內部狀態的改變每每會帶來事物的行爲改變。好比電燈的開關是開仍是關,在外界的表現就徹底不一樣。程序員
按照常規思路,實現一個電燈就是構造一個電燈類,而後指定一下它的開關是什麼,每次開關改變,觸發電燈相應的方法。數組
1 var Light = function(){ 2 this.state = 'off'; // 給電燈設置初始狀態off
3 this.button = null; // 還沒有指定按鈕
4 }; 5
6 Light.prototype.init = function(){ //初始化,創造一個按鈕給電燈對象
7 var button = document.createElement( 'button' ), 8 self = this; //保存引用
9 button.innerHTML = '開關'; 10 this.button = document.body.appendChild( button ); 11 this.button.onclick = function(){ //點一次開關調用一次對象的change方法
12 self.change(); 13 } 14 }; 15
16 Light.prototype.change = function(){ 17 if ( this.state === 'off' ){ 18 console.log( '開燈' ); 19 this.state = 'on'; 20 }else if ( this.state === 'on' ){ 21 console.log( '關燈' ); 22 this.state = 'off'; 23 } 24 }; 25
26 var light = new Light(); 27 light.init();
這段代碼是很是常規的實現,邏輯上也很容易理解,但有個小問題,全部的狀態是寫死在change方法裏的,假若咱們想要添加一些狀態就得深刻進去修改,並且,想要知道對象一共有多少狀態也得進去一個一個數,在代碼量不少的狀況下,change方法也會變得很臃腫。瀏覽器
若是想要依次改變狀態,並且能夠知道一共有多少種狀態,感受經過數組徹底能夠實現,在light屬性裏定義一個狀態數組,light的當前狀態就是數組的第一個元素,每當點擊時把狀態改爲數組的下一位。並且經過獲取數組長度也很容易知道一共有多少狀態,且能所有打印出來,修改起來也很方便。app
1 var Light = function(){ 2 this.stateArr=["off","on","small_light"]; //三個狀態,關閉,點亮,暗一點
3 this.state = this.stateArr[0]; // 給電燈設置初始狀態off
4 this.button = null; // 還沒有指定按鈕
5 }; 6
7 Light.prototype.change=function(){ 8
9 var num=this.stateArr.indexOf(this.state); 10 num=(num==this.stateArr.length-1)?0:num+1; 11 this.state=this.stateArr[num]; 12 console.log( this.state ); 13 }
改動以後每次點擊依次觸發 "on" "small_light" "off"。函數
不過如今還有一個問題,咱們只是作到了能夠依次改變狀態和隨便新增狀態,可是change函數本質上也僅僅能夠遍歷狀態而已,須要每種狀態執行不一樣的函數時,如今的代碼仍是無能爲力。咱們但願的是,change函數遍歷到對的狀態,有一句通用的代碼能夠執行正確的函數。測試
因此,僅僅是把狀態變成數組是不夠的,咱們須要的是一個對象,裏面不但存儲了狀態,還應該存儲了相應方法。咱們統一這些方法的名稱,在change函數裏就能夠統一調用。this
1 /****新建off類***/
2 var off=function(){ 3 this.statename="off"; 4 } 5 off.prototype.do=function(){ 6 console.log("關燈啦"); 7 } 8
9 /****新建on類****/
10 var on=function(){ 11 this.statename="on"; 12 } 13 on.prototype.do=function(){ 14 console.log("開燈啦"); 15 } 16
17 /****新建small_light類****/
18 var small_light=function(){ 19 this.statename="small_light"; 20 } 21 small_light.prototype.do=function(){ 22 console.log("光線變暗啦"); 23 } 24
25 var Light = function(){ 26 this.stateArr=[]; //存儲狀態對象
27 this.stateArr.push(new off()); //添加狀態對象,若是要增刪或者調整順序,經過操做數組或者這裏手動修改均可以作到
28 this.stateArr.push(new on()); 29 this.stateArr.push(new small_light()); 30
31 this.stateobj=this.stateArr[0]; //指定初始狀態對象
32 this.state = this.stateobj.statename; // 給電燈設置初始狀態
33 this.button = null; // 還沒有指定按鈕
34 }; 35
36 Light.prototype.init = function(){ //初始化,創造一個按鈕給電燈對象
37 var button = document.createElement( 'button' ), 38 self = this; 39 button.innerHTML = '開關'; 40 this.button = document.body.appendChild( button ); 41 this.button.onclick = function(){ //點一次開關調用一次對象的change方法
42 self.change(); 43 } 44 }; 45
46 Light.prototype.change = function(){ 47 var num=this.stateArr.indexOf(this.stateobj); //返回當前狀態對象在數組的索引
48 num=(num==this.stateArr.length-1)?0:num+1; //根據索引作些處理
49
50 this.stateobj=this.stateArr[num]; //從新指定狀態對象
51 this.state=this.stateobj.statename; //改變狀態
52 this.stateobj.do(); //執行新狀態對象的代碼
53 }; 54
55 var light = new Light(); 56 light.init(); 57
58 /****點擊按鈕執行結果***/
59 // 開燈啦
60 // 光線變暗啦
61 // 關燈啦
62 // 開燈啦
63 //....(不斷重複)
上述代碼能夠自行去瀏覽器的控制檯測試,這裏還有一點小問題,就是接口的統一(do方法)徹底要靠程序員的自覺和記憶力,有的時候,若是在代碼較多的時候,由於沒有接口統一引發bug,調試起來很麻煩,因此咱們想到了模板方法中爲了不這種狀況的作法。讓全部狀態類都產生於一個抽象類,抽象類的do方法中拋出一個錯誤,若是狀態類沒有重寫該方法,在程序執行後也能很快發現錯誤的緣由。spa
優勢:
狀態模式定義了狀態與行爲之間的關係,並將它們封裝在一個類裏。經過增長新的狀態類,很容易增長新的狀態和轉換。prototype
避免Context(上下文)無限膨脹,狀態切換的邏輯被分佈在狀態類中,也去掉了Context中本來過多的條件分支。
缺點:
是會在系統中定義許多狀態類,編寫多個狀態類是一項枯燥乏味的工做,並且系統中會所以而增長很多對象。另外,因爲邏輯分散在狀態類中,雖然避開了不受歡迎的條件分支語句,但也形成了邏輯分散的問題,咱們沒法在一個地方就看出整個狀態機的邏輯。調試
這裏由於例子如此,因此用了數組來管理,而實際的開發中,會出現各類各樣切換狀態的方式,不必定是順序的;並且可能存在兩個或者更多按鈕,他們的點擊都會致使狀態變化,這種時候,就須要根據具體的狀況,完成狀態的切換和執行相應代碼。