狀態 模式 欄目 JavaScript 简体版
原文   原文鏈接

level01:電燈程序

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <script>
        var Light = function(){
            this.state = 'off'; // 給電燈設置初始狀態 off
            this.button = null; // 電燈開關按鈕
        };
        Light.prototype.init = function(){
            var button = document.createElement( 'button' ),
                self = this;
            button.innerHTML = '開關';
            this.button = document.body.appendChild( button );
            this.button.onclick = function(){
                self.buttonWasPressed();
            }
        };
        Light.prototype.buttonWasPressed = function(){
            if ( this.state === 'off' ){
                console.log( '開燈' );
                this.state = 'on';
            }else if ( this.state === 'on' ){
                console.log( '關燈' );
                this.state = 'off';
            }
        };
        var light = new Light();
        light.init();
    </script>
</body>
</html>

  

 

缺點:javascript

  1. 很明顯 buttonWasPressed 方法是違反開放封閉原則的,每次新增或者修改 light 的狀態,都須要改動buttonWasPressed 方法中的代碼,這使得 buttonWasPressed 成爲了一個很是不穩定的方法。
  2. 在狀態多的狀況下buttonWasPressed方法可能會很龐大。
  3. 狀態之間的切換關係,不過是往 buttonWasPressed 方法裏堆砌 if 、 else 語句,增長或者修改一個狀態可能須要改變若干個操做,這使 buttonWasPressed 更加難以閱讀和維護。

level02:狀態模式改進電燈程序

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <script>
        // OffLightState:
        var OffLightState = function( light ){
            this.light = light;
        };
        OffLightState.prototype.buttonWasPressed = function(){
            console.log( '弱光' ); // offLightState 對應的行爲
            this.light.setState( this.light.weakLightState ); // 切換狀態到 weakLightState
        };
        // WeakLightState:
        var WeakLightState = function( light ){
            this.light = light;
        };
        WeakLightState.prototype.buttonWasPressed = function(){
            console.log( '強光' ); // weakLightState 對應的行爲
            this.light.setState( this.light.strongLightState ); // 切換狀態到 strongLightState
        };
        // StrongLightState:
        var StrongLightState = function( light ){
            this.light = light;
        };
        StrongLightState.prototype.buttonWasPressed = function(){
            console.log( '關燈' ); // strongLightState 對應的行爲
            this.light.setState( this.light.offLightState ); // 切換狀態到 offLightState
        };
        var Light = function(){
            this.offLightState = new OffLightState( this );
            this.weakLightState = new WeakLightState( this );
            this.strongLightState = new StrongLightState( this );
            this.button = null;
        };
        Light.prototype.init = function(){
            var button = document.createElement( 'button' ),
                self = this;
            this.button = document.body.appendChild( button );
            this.button.innerHTML = '開關';
            this.currState = this.offLightState; // 設置當前狀態
            this.button.onclick = function(){
                self.currState.buttonWasPressed();
            }
        };
        Light.prototype.setState = function( newState ){
            this.currState = newState;
        };

        var light = new Light();
        light.init();
    </script>
</body>
</html>

tips:

在前面的電燈例子中,咱們完成了一個狀態模式程序的編寫。首先定義了 Light 類, Light類在這裏也被稱爲上下文(Context)。隨後在 Light 的構造函數中,咱們要建立每個狀態類的實例對象,Context 將持有這些狀態對象的引用,以便把請求委託給狀態對象。html

咱們要編寫各類狀態類, light 對象被傳入狀態類的構造函數,狀態對象也須要持有 light 對象的引用,以便調用 light 中的方法或者直接操做 light 對象:java

var OffLightState = function( light ){
    this.light = light;
};
OffLightState.prototype.buttonWasPressed = function(){
    console.log( '弱光' );
    this.light.setState( this.light.weakLightState );
};

  


狀態模式的定義:

容許一個對象在其內部狀態改變時改變它的行爲,對象看起來彷佛修改了它的類。 
咱們以逗號分割,把這句話分爲兩部分來看。第一部分的意思是將狀態封裝成獨立的類,並將請求委託給當前的狀態對象,當對象的內部狀態改變時,會帶來不一樣的行爲變化。電燈的例子足以說明這一點,在 off和 on這兩種不一樣的狀態下,咱們點擊同一個按鈕,獲得的行爲反饋是大相徑庭的。程序員


level03:狀態模式改進電燈程序

憾的是,JavaScript既不支持抽象類,也沒有接口的概念。因此在使用狀態模式的時候要格外當心,若是咱們編寫一個狀態子 
類時,忘記了給這個狀態子類實現 buttonWasPressed 方法,則會在狀態切換的時候拋出異常。由於 Context老是把請求委託給狀態對象的 buttonWasPressed 方法。算法

不論怎樣嚴格要求程序員,也許都避免不了犯錯的那一天,畢竟若是沒有編譯器的幫助,只依靠程序員的自覺以及一點好運氣,是不靠譜的。這裏建議的解決方案跟《模板方法模式》中一致,讓抽象父類的抽象方法直接拋出一個異常,這個異常至少會在程序運行期間就被發現:性能優化

var State = function(){};
    State.prototype.buttonWasPressed = function(){
        throw new Error( '父類的 buttonWasPressed 方法必須被重寫' );
    };
    var SuperStrongLightState = function( light ){
        this.light = light;
    };
    SuperStrongLightState.prototype = new State(); // 繼承抽象父類
    SuperStrongLightState.prototype.buttonWasPressed = function(){ // 重寫 buttonWasPressed 方法
        console.log( '關燈' );
        this.light.setState( this.light.offLightState );
    };

  


狀態模式的優缺點

  1. 狀態模式定義了狀態與行爲之間的關係,並將它們封裝在一個類裏。經過增長新的狀態類,很容易增長新的狀態和轉換。
  2. 避免 Context 無限膨脹,狀態切換的邏輯被分佈在狀態類中,也去掉了 Context 中本來過多的條件分支。
  3. Context中的請求動做和狀態類中封裝的行爲能夠很是容易地獨立變化而互不影響。

狀態模式中的性能優化點

  1. 有兩種選擇來管理 state 對象的建立和銷燬。第一種是僅當 state 對象被須要時才建立並隨後銷燬,另外一種是一開始就建立好全部的狀態對象,而且始終不銷燬它們。若是 state對象比較龐大,能夠用第一種方式來節省內存,這樣能夠避免建立一些不會用到的對象並及時地回收它們。但若是狀態的改變很頻繁,最好一開始就把這些 state 對象都建立出來,也沒有必要銷燬它們,由於可能很快將再次用到它們。
  2. 在本章的例子中,咱們爲每一個 Context 對象都建立了一組 state 對象,實際上這些 state對象之間是能夠共享的,各Context 對象能夠共享一個 state 對象,這也是享元模式的應 
    用場景之一。

狀態模式和策略模式的關係

狀態模式和策略模式像一對雙胞胎,它們都封裝了一系列的算法或者行爲,它們的類圖看起來幾乎如出一轍,但在乎圖上有很大不一樣,所以它們是兩種迥然不一樣的模式。app

策略模式和狀態模式的相同點是,它們都有一個上下文、一些策略或者狀態類,上下文把請求委託給這些類來執行。函數

它們之間的區別是策略模式中的各個策略類之間是平等又平行的,它們之間沒有任何聯繫,因此客戶必須熟知這些策略類的做用,以便客戶能夠隨時主動切換算法;而在狀態模式中,狀態和狀態對應的行爲是早已被封裝好的,狀態之間的切換也早被規定完成,「改變行爲」這件事情發生在狀態模式內部。對客戶來講,並不須要瞭解這些細節。這正是狀態模式的做用所在。性能


level02:JavaScript 版本的狀態機

狀態模式是狀態機的實現之一,但在 JavaScript這種「無類」語言中,沒有規定讓狀態對象必定要從類中建立而來。另一點,JavaScript 能夠很是方便地使用委託技術,並不須要事先讓一個對象持有另外一個對象。下面的狀態機選擇了經過Function.prototype.call 方法直接把請求委託給某個字面量對象來執行。優化

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <script>
        var Light = function(){
            this.button = null;
        };
        Light.prototype.init = function(){
            var button = document.createElement( 'button' ),
                self = this;
            button.innerHTML = '已關燈';
            this.currState = FSM.off; // 設置當前狀態
            this.button = document.body.appendChild( button );
            this.button.onclick = function(){
                self.currState.buttonWasPressed.call( self ); // 把請求委託給 FSM 狀態機
            }
        };
        var FSM = {
            off: {
                buttonWasPressed: function(){
                    console.log( '關燈' );
                    this.button.innerHTML = '下一次按我是開燈';
                    this.currState = FSM.on;
                }
            },
            on: {
                buttonWasPressed: function(){
                    console.log( '開燈' );
                    this.button.innerHTML = '下一次按我是關燈';
                    this.currState = FSM.off;
                }
            }
        };
        var light = new Light();
        light.init();
    </script>
</body>

</html>
相關文章
相關標籤/搜索
每日一句
    每一个你不满意的现在,都有一个你没有努力的曾经。
本站公眾號
   歡迎關注本站公眾號,獲取更多信息