Android編程設(shè)計(jì)模式之狀態(tài)模式詳解
本文實(shí)例講述了Android編程設(shè)計(jì)模式之狀態(tài)模式。分享給大家供大家參考,具體如下:
一、介紹
狀態(tài)模式中的行為是由狀態(tài)來決定的,不同的狀態(tài)下有不同的行為。狀態(tài)模式和策略模式的結(jié)構(gòu)幾乎完全一樣,但它們的目的、本質(zhì)卻完全不一樣。狀態(tài)模式的行為是平行的、不可替換的,策略模式的行為是彼此獨(dú)立、可相互替換的。用一句話來表述,狀態(tài)模式把對象的行為包裝在不同的狀態(tài)對象里,每一個(gè)狀態(tài)對象都有一個(gè)共同的抽象狀態(tài)基類。狀態(tài)模式的意圖是讓一個(gè)對象在其內(nèi)部狀態(tài)改變的時(shí)候,其行為也隨之改變。
二、定義
當(dāng)一個(gè)對象的內(nèi)在狀態(tài)改變時(shí)允許改變其行為,這個(gè)對象看起來像是改變了其類。
三、使用場景
(1)一個(gè)對象的行為取決于它的狀態(tài),并且它必須在運(yùn)行時(shí)根據(jù)狀態(tài)改變它的行為。
(2)代碼中包含大量與對象狀態(tài)有關(guān)的條件語句,例如,一個(gè)操作中含有龐大的多分支語句(if-else或switch-case),且這些分支依賴于該對象的狀態(tài)。
狀態(tài)模式將每一個(gè)條件分支放入一個(gè)獨(dú)立的類中,這使得你可以根據(jù)對象自身的情況將對象的狀態(tài)作為一個(gè)對象,這一對象可以不依賴與其他對象而獨(dú)立變化,這樣通過多態(tài)去除過多的、重復(fù)的if-else等分支語句。
四、狀態(tài)模式的UML類圖
UML類圖:

角色介紹:
Context:環(huán)境類,定義客戶感興趣的接口,維護(hù)一個(gè)State子類的實(shí)例,這個(gè)實(shí)例定義了對象的當(dāng)前狀態(tài)。
State:抽象狀態(tài)類或狀態(tài)接口,定義一個(gè)或者一組接口,表示該狀態(tài)下的行為。
ConcreteStateA、ConcreteStateB:具體狀態(tài)類,每一個(gè)具體的狀態(tài)類實(shí)現(xiàn)抽象State中定義的接口,從而達(dá)到不同狀態(tài)下的不同行為。
五、簡單示例
下面我們就以電視遙控器為例來演示一下狀態(tài)模式的實(shí)現(xiàn)。我們首先將電視的狀態(tài)簡單分為開機(jī)狀態(tài)和關(guān)機(jī)狀態(tài),在開機(jī)狀態(tài)下可以通過遙控器進(jìn)行頻道切換、調(diào)整音量等操作,但是,此時(shí)重復(fù)按開機(jī)鍵是無效的;而在關(guān)機(jī)狀態(tài)下,頻道切換、調(diào)整音量、關(guān)機(jī)都是無效的操作,只有按開機(jī)按鈕時(shí)才會(huì)生效。也就是說電視的內(nèi)部狀態(tài)決定了遙控器的行為。
首先是普通的實(shí)現(xiàn)方法:
public class TVController {
//開機(jī)狀態(tài)
private final static int POWER_ON = 1;
//關(guān)機(jī)狀態(tài)
private final static int POWER_OFF = 2;
//默認(rèn)狀態(tài)
private int mState = POWER_OFF;
public void powerOn(){
if(mState ==POWER_OFF){
System.out.println("電視開機(jī)了");
}
mState = POWER_ON;
}
public void powerOff(){
if(mState ==POWER_ON){
System.out.println("電視關(guān)機(jī)了");
}
mState = POWER_OFF;
}
public void nextChannel(){
if(mState ==POWER_ON){
System.out.println("下一頻道");
}else{
System.out.println("沒有開機(jī)");
}
}
public void prevChannel(){
if(mState ==POWER_ON){
System.out.println("上一頻道");
}else{
System.out.println("沒有開機(jī)");
}
}
public void turnUp(){
if(mState ==POWER_ON){
System.out.println("調(diào)高音量");
}else{
System.out.println("沒有開機(jī)");
}
}
public void turnDown(){
if(mState ==POWER_ON){
System.out.println("調(diào)低音量");
}else{
System.out.println("沒有開機(jī)");
}
}
}
可以看到,每次執(zhí)行通過判斷當(dāng)前狀態(tài)來進(jìn)行操作,部分的代碼重復(fù),假設(shè)狀態(tài)和功能增加,就會(huì)越來越難以維護(hù)。這時(shí)可以使用狀態(tài)模式,如下:
電視的狀態(tài)接口:
/**
* 電視狀態(tài)接口,定義了電視的操作函數(shù)
*
**/
public interface TVState {
public void nextChannel();
public void prevChannel();
public void turnUp();
public void turnDown();
}
關(guān)機(jī)狀態(tài):
/**
*
* 關(guān)機(jī)狀態(tài),操作無結(jié)果
*
* */
public class PowerOffState implements TVState{
@Override
public void nextChannel() {
}
@Override
public void prevChannel() {
}
@Override
public void turnUp() {
}
@Override
public void turnDown() {
}
}
開機(jī)狀態(tài):
/**
*
* 開機(jī)狀態(tài),操作有效
*
* */
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("調(diào)高音量");
}
@Override
public void turnDown() {
System.out.println("調(diào)低音量");
}
}
電源操作接口:
/**
* 電源操作接口
*
* */
public interface PowerController {
public void powerOn();
public void powerOff();
}
電視遙控器:
/**
* 電視遙控器
*
* */
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("開機(jī)了");
}
@Override
public void powerOff() {
setTVState(new PowerOffState());
System.out.println("關(guān)機(jī)了");
}
public void nextChannel(){
mTVState.nextChannel();
}
public void prevChannel(){
mTVState.prevChannel();
}
public void turnUp(){
mTVState.turnUp();
}
public void turnDown(){
mTVState.turnDown();
}
}
調(diào)用:
public class Client {
public static void main(String[] args) {
TVController tvController = new TVController();
//設(shè)置開機(jī)狀態(tài)
tvController.powerOn();
//下一頻道
tvController.nextChannel();
//調(diào)高音量
tvController.turnUp();
//關(guān)機(jī)
tvController.powerOff();
//調(diào)低音量,此時(shí)不會(huì)生效
tvController.turnDown();
}
}
輸出結(jié)果如下:
開機(jī)了 下一頻道 調(diào)高音量 關(guān)機(jī)了
上述實(shí)現(xiàn)中,我們抽象了一個(gè)TVState接口,該接口中有操作電視的所有函數(shù),該接口有兩個(gè)實(shí)現(xiàn)類,即開機(jī)狀態(tài)(PowerOnState)和關(guān)機(jī)狀態(tài)(PowerOffState)。開機(jī)狀態(tài)下只有開機(jī)功能是無效的,也就是說在已經(jīng)開機(jī)的時(shí)候用戶在按開機(jī)鍵不會(huì)產(chǎn)生任何反應(yīng);而在關(guān)機(jī)狀態(tài)下,只有開機(jī)功能是可用的,其他功能都不會(huì)生效。同一個(gè)操作,如調(diào)高音量的turnUp函數(shù),在關(guān)機(jī)狀態(tài)下無效,在開機(jī)狀態(tài)下就會(huì)將電視的音量調(diào)高,也就是說電視內(nèi)部狀態(tài)影響了電視遙控器的行為。狀態(tài)模式將這些行為封裝到狀態(tài)類中,在進(jìn)行操作時(shí)將這些功能轉(zhuǎn)發(fā)給狀態(tài)對象,不同的狀態(tài)有不同的實(shí)現(xiàn),這樣就通過多態(tài)的形式去除了重復(fù)、雜亂的if-else語句,這也正是狀態(tài)模式的精髓所在。
六、Android實(shí)戰(zhàn)中的使用
1、登錄系統(tǒng),根據(jù)用戶是否登錄,判斷事件的處理方式。
2、Wi-Fi管理,在不同的狀態(tài)下,WiFi的掃描請求處理不一。
下面以登錄系統(tǒng)為例講解下狀態(tài)模式在實(shí)戰(zhàn)中的使用:
在android開發(fā)中,我們遇到登錄界面是十分常見的,而狀態(tài)設(shè)計(jì)模式在登錄界面的應(yīng)用十分廣泛,用戶在登錄狀態(tài)下和未登錄狀態(tài)下,對邏輯的操作是不一樣的。例如最常見的情況就是在玩新浪微博的時(shí)候,用戶在登錄的情況下才能完成評論和轉(zhuǎn)發(fā)微博的操作;而當(dāng)用戶處于未登錄的情況下要執(zhí)行轉(zhuǎn)發(fā)和評論微博的操作需要進(jìn)入登錄界面登錄以后才能執(zhí)行,所以面對這兩者不同的狀況,利用狀態(tài)設(shè)計(jì)模式來設(shè)計(jì)這個(gè)例子最好不過。
1、狀態(tài)基類
前面我們講過狀態(tài)設(shè)計(jì)模式的原理實(shí)則是多態(tài),在這里我們用UserState接口表示此基類,包換轉(zhuǎn)發(fā)操作和評論這兩種狀態(tài),代碼如下:
public interface UserState {
/**
* 轉(zhuǎn)發(fā)操作
* @param context
*/
public void forword(Context context);
/**
* 評論操作
* @param context
*/
public void commit(Context context);
}
2、用戶在登錄和未登錄兩種狀況下的實(shí)現(xiàn)類LoginState和LogoutState;代碼如下:
在LoginState.java中,用戶是可以執(zhí)行轉(zhuǎn)發(fā)和評論操作。
public class LoginState implements UserState{
@Override
public void forword(Context context) {
Toast.makeText(context, "轉(zhuǎn)發(fā)成功", Toast.LENGTH_SHORT).show();
}
@Override
public void commit(Context context) {
Toast.makeText(context, "評論成功", Toast.LENGTH_SHORT).show();
}
}
在LogoutState.java中,用戶在未登錄的情況下不允許執(zhí)行操作,而是應(yīng)該跳轉(zhuǎn)到登錄界面執(zhí)行登錄以后才可以執(zhí)行。
public class LogoutState implements UserState{
/**
* 跳轉(zhuǎn)到登錄界面登錄以后才能轉(zhuǎn)發(fā)
*/
@Override
public void forword(Context context) {
gotoLohinActivity(context);
}
/**
* 跳轉(zhuǎn)到登錄界面登錄以后才能評論
*/
@Override
public void commit(Context context) {
gotoLohinActivity(context);
}
/**
* 界面跳轉(zhuǎn)操作
* @param context
*/
private void gotoLohinActivity(Context context){
context.startActivity(new Intent(context,LoginActivity.class));
}
}
3、操作角色LoginContext
這里的LoginContext就是在狀態(tài)模式的Context角色,是用戶操作對象和管理對象,LoginContext委托相關(guān)的操作給狀態(tài)對象,在其中狀態(tài)的發(fā)生改變,LoginContext的行為也發(fā)生改變。LoginContext的代碼如*下:
溫馨提示:
這里我們用到單例就是為了全局只有一個(gè)LoginContext去控制用戶狀態(tài);
public class LoginContext {
//用戶狀態(tài)默認(rèn)為未登錄狀態(tài)
UserState state = new LogoutState();
private LoginContext(){};//私有構(gòu)造函數(shù),避免外界可以通過new 獲取對象
//單例模式
public static LoginContext getInstance(){
return SingletonHolder.instance;
}
/**
*靜態(tài)代碼塊
*/
private static class SingletonHolder{
private static final LoginContext instance = new LoginContext();
}
public void setState(UserState state){
this.state = state;
}
//轉(zhuǎn)發(fā)
public void forward(Context context){
state.forword(context);
}
//評論
public void commit(Context context){
state.commit(context);
}
}
4、界面展示
LoginActivity.java,此界面執(zhí)行登錄操作,登錄成后把 LoginContext.getInstance().setState(new LoginState());設(shè)置為登錄狀態(tài),在MainActivity中就執(zhí)行的是登錄狀態(tài)下的操作,即可以轉(zhuǎn)發(fā)可評論;
public class LoginActivity extends Activity implements OnClickListener{
private static final String LOGIN_URL = "http://10.10.200.193:8080/Day01/servlet/LoginServlet";
private EditText et_username;
private EditText et_password;
private Button btn_login;
private String username;
private String password;
private KJHttp http;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
initView();
initData();
}
private void initView() {
et_username = (EditText) findViewById(R.id.et_username);
et_password = (EditText) findViewById(R.id.et_password);
btn_login = (Button) findViewById(R.id.btn_login);
btn_login.setOnClickListener(LoginActivity.this);
}
private void initData() {
http = new KJHttp();
}
/**
* 執(zhí)行登錄操作
*
* @param username2
* @param password2
*/
protected void sendLogin(String username2, String password2) {
HttpParams params = new HttpParams();
params.put("username", "user1");
params.put("password", "123456");
http.post(LOGIN_URL, params, new HttpCallBack() {
@Override
public void onSuccess(String t) {
if ("200".equals(t)) {
//設(shè)置為登錄狀態(tài)
LoginContext.getInstance().setState(new LoginState());
startActivity(new Intent(LoginActivity.this,MainActivity.class));
finish();
Toast.makeText(LoginActivity.this, "登錄成功", Toast.LENGTH_SHORT).show();
}
}
});
}
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn_login:
username = et_username.getEditableText().toString().trim();
password = et_password.getEditableText().toString().trim();
if (TextUtils.isEmpty(username) || TextUtils.isEmpty(password)) {
Toast.makeText(LoginActivity.this, "用戶名密碼不能為空", Toast.LENGTH_SHORT).show();
return;
}
sendLogin(username, password);
break;
}
}
}
MainActivity.java,在用戶登錄成功后,點(diǎn)擊轉(zhuǎn)發(fā)和評論執(zhí)行的是登錄狀態(tài)下的操作,而當(dāng)用戶注銷時(shí),我們把LoginContext的狀態(tài)設(shè)置為未登錄狀態(tài);LoginContext.getInstance().setState(new LogoutState());此時(shí)在點(diǎn)擊轉(zhuǎn)發(fā)和評論操作時(shí)就會(huì)跳到用戶登錄界面。
public class MainActivity extends Activity {
private Button btn_forward;
private Button btn_commit;
private Button btn_logout;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initView();
initListener();
}
private void initView() {
btn_forward = (Button) findViewById(R.id.btn_forward);
btn_commit = (Button) findViewById(R.id.btn_commit);
btn_logout = (Button) findViewById(R.id.btn_logout);
}
private void initListener() {
//轉(zhuǎn)發(fā)操作
btn_forward.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
//調(diào)用LoginContext里面的轉(zhuǎn)發(fā)函數(shù)
LoginContext.getInstance().forward(MainActivity.this);
}
});
//評論操作
btn_commit.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
//調(diào)用LoginContext里面的轉(zhuǎn)發(fā)函數(shù)
LoginContext.getInstance().commit(MainActivity.this);
}
});
//注銷操作
btn_logout.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
//設(shè)置為注銷狀態(tài)
LoginContext.getInstance().setState(new LogoutState());
}
});
}
}
七、總結(jié)
狀態(tài)模式的關(guān)鍵點(diǎn)在于不同的狀態(tài)下對于同一行為有不同的響應(yīng),這其實(shí)就是一個(gè)將if-else用多態(tài)來實(shí)現(xiàn)的一個(gè)具體示例。在if-else或者switch-case形式下根據(jù)不同的狀態(tài)進(jìn)行判斷,如果是狀態(tài)A那么執(zhí)行方法A,狀態(tài)B執(zhí)行方法B,但這種實(shí)現(xiàn)使得邏輯耦合在一起,易于出錯(cuò),通過狀態(tài)模式能夠很好的消除這類”丑陋“的邏輯處理,當(dāng)然并不是任何出現(xiàn)if-else的地方都應(yīng)該通過狀態(tài)模式重構(gòu),模式的運(yùn)用一定要考慮所處的情景以及你要解決的問題,只有符合特定的場景才建議使用對應(yīng)的模式。
優(yōu)點(diǎn):
將所有與一個(gè)特定的狀態(tài)相關(guān)的行為都放入一個(gè)狀態(tài)對象中,它提供了一個(gè)更好的方法來組織與特定狀態(tài)相關(guān)的代碼,將繁瑣的狀態(tài)判斷轉(zhuǎn)換成結(jié)構(gòu)清晰的狀態(tài)類族,在避免代碼膨脹的同時(shí)也保證了可擴(kuò)展性與可維護(hù)性。
缺點(diǎn):
狀態(tài)模式的使用必然會(huì)增加系統(tǒng)類和對象的個(gè)數(shù)。
更多關(guān)于Android相關(guān)內(nèi)容感興趣的讀者可查看本站專題:《Android開發(fā)入門與進(jìn)階教程》、《Android調(diào)試技巧與常見問題解決方法匯總》、《Android基本組件用法總結(jié)》、《Android視圖View技巧總結(jié)》、《Android布局layout技巧總結(jié)》及《Android控件用法總結(jié)》
希望本文所述對大家Android程序設(shè)計(jì)有所幫助。
- Android編程設(shè)計(jì)模式之訪問者模式詳解
- Android編程設(shè)計(jì)模式之模板方法模式詳解
- Android編程設(shè)計(jì)模式之迭代器模式詳解
- Android編程設(shè)計(jì)模式之備忘錄模式詳解
- Android編程設(shè)計(jì)模式之命令模式詳解
- Android編程設(shè)計(jì)模式之解釋器模式詳解
- Android編程設(shè)計(jì)模式之責(zé)任鏈模式詳解
- Android編程設(shè)計(jì)模式之策略模式詳解
- Android編程設(shè)計(jì)模式之抽象工廠模式詳解
- android設(shè)計(jì)模式之單例模式詳解
- Android編程設(shè)計(jì)模式之中介者模式詳解
相關(guān)文章
淺談Android App開發(fā)中Fragment的創(chuàng)建與生命周期
這篇文章主要介紹了Android App開發(fā)中Fragment的創(chuàng)建與生命周期,文中詳細(xì)地介紹了Fragment的概念以及一些常用的生命周期控制方法,需要的朋友可以參考下2016-02-02
Android中ViewPager實(shí)現(xiàn)滑動(dòng)條及與Fragment結(jié)合的實(shí)例教程
ViewPager類主要被用來實(shí)現(xiàn)可滑動(dòng)的視圖功能,這里我們就來共同學(xué)習(xí)Android中ViewPager實(shí)現(xiàn)滑動(dòng)條及與Fragment結(jié)合的實(shí)例教程,需要的朋友可以參考下2016-06-06
Android開發(fā)實(shí)現(xiàn)NFC刷卡讀取的兩種方式
這篇文章主要為大家詳細(xì)介紹了Android開發(fā)中實(shí)現(xiàn)NFC刷卡讀取的兩種方式,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-09-09
Android Studio 下自動(dòng)注釋(自定義作者,類作用等)圖文詳解
android studio 下自動(dòng)注釋功能居然被隱藏了,很多功能都不見了,下面小編通過本文給大家分享Android Studio 下自動(dòng)注釋(自定義作者,類作用等)圖文詳解,需要的朋友參考下吧2017-11-11
Android?ScrollView實(shí)現(xiàn)滾動(dòng)超過邊界松手回彈
這篇文章主要為大家詳細(xì)介紹了Android?ScrollView實(shí)現(xiàn)滾動(dòng)超過邊界松手回彈,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-04-04
微信公眾平臺(tái)開發(fā)入門教程(SAE方倍工作室)
在這篇微信公眾平臺(tái)開發(fā)教程中,我們假定你已經(jīng)有了PHP語言程序、MySQL數(shù)據(jù)庫、計(jì)算機(jī)網(wǎng)絡(luò)通訊、及HTTP/XML/CSS/JS等基礎(chǔ)2014-05-05
解決EditText不顯示光標(biāo)的三種方法(總結(jié))
下面小編就為大家?guī)硪黄鉀QEditText不顯示光標(biāo)的三種方法(總結(jié))。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-04-04
ImageView點(diǎn)擊可變暗的實(shí)例代碼(android代碼技巧)
本文給大家分享一段實(shí)例代碼給大家介紹ImageView點(diǎn)擊可變暗的實(shí)例代碼,非常不錯(cuò),具有參考借鑒價(jià)值,需要的的朋友參考下吧2017-02-02
android創(chuàng)建手勢識(shí)別示例代碼
使用一些瀏覽器或者輸入法應(yīng)用時(shí)會(huì)有一些手勢操作,還可以自定義手勢。這些神奇的操作是怎么做的呢?這一篇重點(diǎn)記錄手勢的識(shí)別和創(chuàng)建2014-01-01

