隨遇而安——狀態模式

《Android源碼設計模式解析與實戰》讀書筆記(八) 《Android源碼設計模式解析與實戰》PDF資料下載php

1、狀態模式的簡介

狀態模式中的行爲是由狀態來決定的,不一樣的狀態下有不一樣的行爲。狀態模式和策略模式的結構幾乎徹底同樣,但它們的目的、本質卻徹底不同。設計模式

狀態模式的行爲是平行的、不可替換的,策略模式的行爲是彼此獨立、可相互替換的。bash

狀態模式把對象的行爲包裝在不一樣的狀態對象裏,每個狀態對象都有一個共同的抽象狀態基類。微信

狀態模式的意圖是讓一個對象在其內部狀態改變的時候,其行爲也隨之改變。網絡

1.一、定義

當一個對象的內在狀態改變時容許改變其行爲,這個對象看起來像是改變了其類。ide

1.二、使用場景

  1. 一個對象的行爲取決於它的狀態,而且它必須在運行時根據狀態改變它的行爲。
  2. 代碼中包含大量與對象狀態有關的條件語句,例如,一個操做中含有龐大的多分支語句,且這些分支依賴於該對象的狀態。

狀態模式將每個條件分支放入一個獨立的類中,這使得咱們能夠根據對象自身的狀況將對象的狀態做爲一個對象,這一對象能夠不依賴於其餘對象而獨立變化,經過多態來去除過多的、重複的if-else等分支語句。函數

2、狀態模式的簡單示例

下面以電視遙控器爲例來演示一下狀態模式的實現。ui

初版代碼:this

/**
 * 電視遙控器,含有開機、關機、下一頻道、上一頻道、調高音量、調低音量這幾個功能
 */
public class TvController {
    //開機狀態
    private final static int POWER_ON = 1;
    //關機狀態
    private final static int POWER_OFF = 2;
    private int mState = POWER_OFF;

    public void powerOn() {
        mState = POWER_ON;
        if (mState == POWER_OFF) {
            System.out.println("開機啦!");
        }
    }

    public void powerOff() {
        mState = POWER_OFF;
        if (mState == POWER_ON) {
            System.out.println("關機啦!");
        }
    }

    public void nextChannel() {
        if (mState == POWER_ON) {
            System.out.println("下一頻道");
        } else {
            System.out.println("兩個紅燈提示沒有開機");
        }
    }

    public void prevChannel() {
        if (mState == POWER_ON) {
            System.out.println("上一頻道");
        } else {
            System.out.println("兩個紅燈提示沒有開機");
        }
    }

    public void turnUp() {
        if (mState == POWER_ON) {
            System.out.println("調高音量");
        } else {
            System.out.println("兩個紅燈提示沒有開機");
        }
    }

    public void turnDown() {
        if (mState == POWER_ON) {
            System.out.println("調低音量");
        } else {
            System.out.println("兩個紅燈提示沒有開機");
        }
    }
}
複製代碼

在TvController類中,經過mState字段存儲了電視的狀態,而且在各個操做中根據狀態來判斷是否執行。所以致使了在每一個功能中都須要使用if-else,代碼重複、相對較爲混亂。spa

狀態模式即便爲解決這類的問題而出現的,以下代碼:

//電視狀態接口,定義了電視操做的函數
public interface TvState {
    public void nextChannel();
    public void prevChannel();
    public void turnUp();
    public void turnDown();
}
複製代碼
/**
 * 開機狀態,此時再觸發開機功能不作任何操做
 */
public class PowerOnState implements TvState {
    @Override
    public void nextChannel() {
        System.out.println("下一頻道");
    }

    @Override
    public void prevChannel() {
        System.out.println("上一頻道");
    }

    @Override
    public void turnUp() {
        System.out.println("調高音量");
    }

    @Override
    public void turnDown() {
        System.out.println("調低音量");
    }
}
複製代碼
/**
 * 關機狀態,此時只有哦開機功能是有效的
 */
public class PowerOffState implements TvState {
    @Override
    public void nextChannel() {

    }

    @Override
    public void prevChannel() {

    }

    @Override
    public void turnUp() {

    }

    @Override
    public void turnDown() {

    }
}
複製代碼
/**
 * 電源操做接口
 */
public interface PowerController {
    public void powerOn();

    public void powerOff();
}
複製代碼
/**
 * 電視遙控器,相似於經典狀態模式中的Context
 */
public class TvController implements PowerController {
    TvState mTvState;

    public void setTvState(TvState mTvState) {
        this.mTvState = mTvState;
    }

    @Override
    public void powerOn() {
        setTvState(new PowerOnState());
        System.out.println("開機了!");
    }

    @Override
    public void powerOff() {
        setTvState(new PowerOffState());
        System.out.println("關機了!");
    }

    public void nextChannel() {
        mTvState.nextChannel();
    }

    public void prevChannel() {
        mTvState.prevChannel();
    }

    public void turnUp() {
        mTvState.turnUp();
    }

    public void turnDown() {
        mTvState.turnDown();
    }
}
複製代碼

調用代碼:

TvController tvController = new TvController();
//設置開機狀態
tvController.powerOn();
//下一頻道
tvController.nextChannel();
//調高音量
tvController.turnUp();
//設置關機狀態
tvController.powerOff();
//調高音量,此時不會生效
tvController.turnUp();
複製代碼

輸出結果:

狀態模式.png

3、狀態模式實戰

在開發過程當中,用到狀態模式最多見的地方應該是用戶登陸系統。在用戶已登陸和未登陸的狀況下,對於同一事件的處理行爲是不同的。

用戶的默認狀態爲未登陸狀態,此時用戶在MainActivity界面點擊轉發時會先跳轉到登陸界面,而後在登陸界面登錄成功後再回到MainActivity頁面,此時用戶字啊進行其餘操做就能夠實現真正的功能。

MainActivity的代碼以下:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        //轉發按鈕
        findViewById(R.id.forward_btn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //調用LoginContext的轉發函數
                LoginContext.getLoginContext().forward(MainActivity.this);
            }
        });

        //註銷功能
        findViewById(R.id.logout_btn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //設置爲註銷狀態
                LoginContext.getLoginContext().setState(new LogoutState());
            }
        });
    }
}
複製代碼

LoginActivity的代碼以下:

public class LoginActivity extends AppCompatActivity {
    EditText userNameEditText,passwordEditText;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        userNameEditText=(EditText)findViewById(R.id.username_edittext);
        passwordEditText = (EditText) findViewById(R.id.password_edittext);
        //登陸按鈕
        findViewById(R.id.login_btn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                login();
                finish();
            }
        });
    }

    private void login() {
        String userName = userNameEditText.getText().toString().trim();
        String pwd = passwordEditText.getText().toString().trim();
        //執行網絡請求,進行登陸.....

        //登陸成功後修改成已登陸狀態
        LoginContext.getLoginContext().setState(new LoginedState());
        Toast.makeText(getApplicationContext(), "登錄成功", Toast.LENGTH_SHORT).show();
    }
}
複製代碼

LoginContext的代碼:

/**
 * 用戶接口和狀態管理類
 */
public class LoginContext {
    //用戶狀態,默認爲未登陸狀態
    UserState mState = new LogoutState();
    //單例
    private static LoginContext sLoginContext = new LoginContext();

    private LoginContext() {
    }

    public static LoginContext getLoginContext() {
        return sLoginContext;
    }

    public void setState(UserState state) {
        mState = state;
    }

    //轉發
    public void forward(Context context) {
        mState.forward(context);
    }

    //評論
    public void comment(Context context) {
        mState.comment(context);
    }
}
複製代碼

用戶狀態接口代碼:

/**
 * 用戶狀態
 */
public interface UserState {
    /**
     * 轉發
     */
    public void forward(Context context);

    /**
     * 評論
     */
    public void comment(Context context);
}
複製代碼

已登陸狀態代碼:

/**
 * 已登陸狀態
 */
public class LoginedState implements UserState {

    @Override
    public void forward(Context context) {
        Toast.makeText(context, "轉發功能", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void comment(Context context) {
        Toast.makeText(context, "評論功能", Toast.LENGTH_SHORT).show();
    }
}
複製代碼

未登陸狀態代碼:

/**
 * 註銷狀態,即未登陸狀態
 */
public class LogoutState implements UserState {

    @Override
    public void forward(Context context) {
        gotoLoginActivity(context);
    }

    @Override
    public void comment(Context context) {
        gotoLoginActivity(context);
    }

    private void gotoLoginActivity(Context context) {
        Intent intent = new Intent(context, LoginActivity.class);
        context.startActivity(intent);
    }

}
複製代碼

4、總結

狀態模式的關鍵點在於不一樣的狀態下對於同一行爲有不一樣的響應,這其實就是一個將if-else用多態來實現的一個具體示例。

4.一、優勢

狀態模式將全部與一個特定的狀態相關的行爲都放入一個狀態對象中,它提供了一個更好的方法來組織與特定狀態的相關代碼,將繁瑣的狀態判斷轉換爲結構清晰的狀態類族,在避免代碼膨脹的同時也保證了可擴展性與可維護性。

4.二、缺點

狀態模式的使用必然會增長系統類和對象的個數。

學海無涯苦做舟

個人微信公衆號
相關文章
相關標籤/搜索
本站公眾號
   歡迎關注本站公眾號,獲取更多信息