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)模式把對(duì)象的行為包裝在不同的狀態(tài)對(duì)象里,每一個(gè)狀態(tài)對(duì)象都有一個(gè)共同的抽象狀態(tài)基類。狀態(tài)模式的意圖是讓一個(gè)對(duì)象在其內(nèi)部狀態(tài)改變的時(shí)候,其行為也隨之改變。
二、定義
當(dāng)一個(gè)對(duì)象的內(nèi)在狀態(tài)改變時(shí)允許改變其行為,這個(gè)對(duì)象看起來像是改變了其類。
三、使用場(chǎng)景
(1)一個(gè)對(duì)象的行為取決于它的狀態(tài),并且它必須在運(yùn)行時(shí)根據(jù)狀態(tài)改變它的行為。
(2)代碼中包含大量與對(duì)象狀態(tài)有關(guān)的條件語句,例如,一個(gè)操作中含有龐大的多分支語句(if-else或switch-case),且這些分支依賴于該對(duì)象的狀態(tài)。
狀態(tài)模式將每一個(gè)條件分支放入一個(gè)獨(dú)立的類中,這使得你可以根據(jù)對(duì)象自身的情況將對(duì)象的狀態(tài)作為一個(gè)對(duì)象,這一對(duì)象可以不依賴與其他對(duì)象而獨(dú)立變化,這樣通過多態(tài)去除過多的、重復(fù)的if-else等分支語句。
四、狀態(tài)模式的UML類圖
UML類圖:
角色介紹:
Context:環(huán)境類,定義客戶感興趣的接口,維護(hù)一個(gè)State子類的實(shí)例,這個(gè)實(shí)例定義了對(duì)象的當(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)下的不同行為。
五、簡(jiǎn)單示例
下面我們就以電視遙控器為例來演示一下狀態(tài)模式的實(shí)現(xiàn)。我們首先將電視的狀態(tài)簡(jiǎn)單分為開機(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)對(duì)象,不同的狀態(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的掃描請(qǐng)求處理不一。
下面以登錄系統(tǒng)為例講解下狀態(tài)模式在實(shí)戰(zhàn)中的使用:
在android開發(fā)中,我們遇到登錄界面是十分常見的,而狀態(tài)設(shè)計(jì)模式在登錄界面的應(yīng)用十分廣泛,用戶在登錄狀態(tài)下和未登錄狀態(tài)下,對(duì)邏輯的操作是不一樣的。例如最常見的情況就是在玩新浪微博的時(shí)候,用戶在登錄的情況下才能完成評(píng)論和轉(zhuǎn)發(fā)微博的操作;而當(dāng)用戶處于未登錄的情況下要執(zhí)行轉(zhuǎn)發(fā)和評(píng)論微博的操作需要進(jìn)入登錄界面登錄以后才能執(zhí)行,所以面對(duì)這兩者不同的狀況,利用狀態(tài)設(shè)計(jì)模式來設(shè)計(jì)這個(gè)例子最好不過。
1、狀態(tài)基類
前面我們講過狀態(tài)設(shè)計(jì)模式的原理實(shí)則是多態(tài),在這里我們用UserState接口表示此基類,包換轉(zhuǎn)發(fā)操作和評(píng)論這兩種狀態(tài),代碼如下:
public interface UserState { /** * 轉(zhuǎn)發(fā)操作 * @param context */ public void forword(Context context); /** * 評(píng)論操作 * @param context */ public void commit(Context context); }
2、用戶在登錄和未登錄兩種狀況下的實(shí)現(xiàn)類LoginState和LogoutState;代碼如下:
在LoginState.java中,用戶是可以執(zhí)行轉(zhuǎn)發(fā)和評(píng)論操作。
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, "評(píng)論成功", 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)到登錄界面登錄以后才能評(píng)論 */ @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角色,是用戶操作對(duì)象和管理對(duì)象,LoginContext委托相關(guān)的操作給狀態(tài)對(duì)象,在其中狀態(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 獲取對(duì)象 //單例模式 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); } //評(píng)論 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ā)可評(píng)論;
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ā)和評(píng)論執(zhí)行的是登錄狀態(tài)下的操作,而當(dāng)用戶注銷時(shí),我們把LoginContext的狀態(tài)設(shè)置為未登錄狀態(tài);LoginContext.getInstance().setState(new LogoutState());
此時(shí)在點(diǎn)擊轉(zhuǎn)發(fā)和評(píng)論操作時(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); } }); //評(píng)論操作 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)下對(duì)于同一行為有不同的響應(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)用一定要考慮所處的情景以及你要解決的問題,只有符合特定的場(chǎng)景才建議使用對(duì)應(yīng)的模式。
優(yōu)點(diǎn):
將所有與一個(gè)特定的狀態(tài)相關(guān)的行為都放入一個(gè)狀態(tài)對(duì)象中,它提供了一個(gè)更好的方法來組織與特定狀態(tài)相關(guān)的代碼,將繁瑣的狀態(tài)判斷轉(zhuǎn)換成結(jié)構(gòu)清晰的狀態(tài)類族,在避免代碼膨脹的同時(shí)也保證了可擴(kuò)展性與可維護(hù)性。
缺點(diǎn):
狀態(tài)模式的使用必然會(huì)增加系統(tǒng)類和對(duì)象的個(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é)》
希望本文所述對(duì)大家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平臺(tái)HttpGet、HttpPost請(qǐng)求實(shí)例
出自網(wǎng)絡(luò)搜索引擎巨頭的Android平臺(tái),其對(duì)網(wǎng)絡(luò)的支持自然不用多說,在Android SDK中已經(jīng)集成了Apache的HttpClient模塊。使用HttpClient模塊,我們就可以使用HTTP協(xié)議進(jìn)行網(wǎng)絡(luò)連接了2014-05-05Android實(shí)現(xiàn)系統(tǒng)消息推送
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)系統(tǒng)消息推送,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-07-07淺析Android 快速實(shí)現(xiàn)圖片壓縮與上傳功能
在Android對(duì)手機(jī)相冊(cè)中的圖片的壓縮和上傳到服務(wù)器上,這樣的功能在每個(gè)app開發(fā)中都會(huì)有這樣的需求.所以今天就對(duì)android端怎么快速實(shí)現(xiàn)圖片壓縮和上傳進(jìn)行簡(jiǎn)單的分析2017-08-08Android實(shí)現(xiàn)簡(jiǎn)易秒表功能
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)簡(jiǎn)易秒表功能,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-09-09Android開發(fā)實(shí)現(xiàn)的簡(jiǎn)單五子棋游戲示例
這篇文章主要介紹了Android開發(fā)實(shí)現(xiàn)的簡(jiǎn)單五子棋游戲,結(jié)合實(shí)例形式分析了Android實(shí)現(xiàn)五子棋游戲功能的布局、游戲功能等具體實(shí)現(xiàn)步驟與相關(guān)算法實(shí)現(xiàn)技巧,需要的朋友可以參考下2017-12-12理解Android的手勢(shì)識(shí)別提高APP的用戶體驗(yàn)
對(duì)于觸摸屏,其原生的消息無非按下、抬起、移動(dòng)這幾種,我們只需要簡(jiǎn)單重載onTouch或者設(shè)置觸摸偵聽器setOnTouchListener即可進(jìn)行處理2013-06-06去掉RecycleView或者ListView上下滑動(dòng)陰影的方法
下面小編就為大家分享一篇去掉RecycleView或者ListView上下滑動(dòng)陰影的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2018-01-01android編程實(shí)現(xiàn)添加文本內(nèi)容到sqlite表中的方法
這篇文章主要介紹了android編程實(shí)現(xiàn)添加文本內(nèi)容到sqlite表中的方法,結(jié)合實(shí)例較為詳細(xì)的分析了Android針對(duì)txt文本文件的讀取及SQL數(shù)據(jù)庫操作的相關(guān)技巧,需要的朋友可以參考下2015-11-11