Android設計模式之——狀態模式

一、介紹

狀態模式中的行為是由狀態來決定的,不同的狀態下有不同的行為。狀態模式和策略模式的結構幾乎完全一樣,但它們的目的、本質卻完全不一樣。狀態模式的行為是平行的、不可替換的,策略模式的行為是彼此獨立、可相互替換的。用一句話來表述,狀態模式把對象的行為包裝在不同的狀態對象里,每一個狀態對象都有一個共同的抽象狀態基類。狀態模式的意圖是讓一個對象在其內部狀態改變的時候,其行為也隨之改變。

二、定義

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

三、使用場景

(1)一個對象的行為取決于它的狀態,并且它必須在運行時根據狀態改變它的行為。
(2)代碼中包含大量與對象狀態有關的條件語句,例如,一個操作中含有龐大的多分支語句(if-else或switch-case),且這些分支依賴于該對象的狀態。

狀態模式將每一個條件分支放入一個獨立的類中,這使得你可以根據對象自身的情況將對象的狀態作為一個對象,這一對象可以不依賴與其他對象而獨立變化,這樣通過多態去除過多的、重復的if-else等分支語句。

四、狀態模式的UML類圖

UML類圖:

這里寫圖片描述

角色介紹:

  • Context:環境類,定義客戶感興趣的接口,維護一個State子類的實例,這個實例定義了對象的當前狀態。

  • State:抽象狀態類或狀態接口,定義一個或者一組接口,表示該狀態下的行為。

  • ConcreteStateA、ConcreteStateB:具體狀態類,每一個具體的狀態類實現抽象State中定義的接口,從而達到不同狀態下的不同行為。

五、簡單示例

下面我們就以電視遙控器為例來演示一下狀態模式的實現。我們首先將電視的狀態簡單分為開機狀態和關機狀態,在開機狀態下可以通過遙控器進行頻道切換、調整音量等操作,但是,此時重復按開機鍵是無效的;而在關機狀態下,頻道切換、調整音量、關機都是無效的操作,只有按開機按鈕時才會生效。也就是說電視的內部狀態決定了遙控器的行為。

首先是普通的實現方法:

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(){if(mState ==POWER_OFF){System.out.println("電視開機了");}mState = POWER_ON;}public void powerOff(){if(mState ==POWER_ON){System.out.println("電視關機了");}mState = POWER_OFF;}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("沒有開機");}}
}

可以看到,每次執行通過判斷當前狀態來進行操作,部分的代碼重復,假設狀態和功能增加,就會越來越難以維護。這時可以使用狀態模式,如下:

電視的狀態接口:

/*** 電視狀態接口,定義了電視的操作函數* **/
public interface TVState {public void nextChannel();public void prevChannel();public void turnUp();public void turnDown();}

關機狀態:

/*** * 關機狀態,操作無結果* * */
public class PowerOffState implements TVState{@Overridepublic void nextChannel() {}@Overridepublic void prevChannel() {}@Overridepublic void turnUp() {}@Overridepublic void turnDown() {}
}

開機狀態:

/*** * 開機狀態,操作有效* * */
public class PowerOnState implements TVState{@Overridepublic void nextChannel() {System.out.println("下一頻道");}@Overridepublic void prevChannel() {System.out.println("上一頻道");}@Overridepublic void turnUp() {System.out.println("調高音量");}@Overridepublic void turnDown() {System.out.println("調低音量");}
}

電源操作接口:

/*** 電源操作接口* * */
public interface PowerController {public void powerOn();public void powerOff();
}

電視遙控器:

/*** 電視遙控器* * */
public class TVController implements PowerController{TVState mTVState;public void setTVState(TVState mTVState){this.mTVState = mTVState;}@Overridepublic void powerOn() {setTVState(new PowerOnState());System.out.println("開機了");}@Overridepublic 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();}
}

調用:

public class Client {public static void main(String[] args) {TVController tvController = new TVController();//設置開機狀態tvController.powerOn();//下一頻道tvController.nextChannel();//調高音量tvController.turnUp();//關機tvController.powerOff();//調低音量,此時不會生效tvController.turnDown();}
}

輸出結果如下:

開機了
下一頻道
調高音量
關機了

上述實現中,我們抽象了一個TVState接口,該接口中有操作電視的所有函數,該接口有兩個實現類,即開機狀態(PowerOnState)和關機狀態(PowerOffState)。開機狀態下只有開機功能是無效的,也就是說在已經開機的時候用戶在按開機鍵不會產生任何反應;而在關機狀態下,只有開機功能是可用的,其他功能都不會生效。同一個操作,如調高音量的turnUp函數,在關機狀態下無效,在開機狀態下就會將電視的音量調高,也就是說電視內部狀態影響了電視遙控器的行為。狀態模式將這些行為封裝到狀態類中,在進行操作時將這些功能轉發給狀態對象,不同的狀態有不同的實現,這樣就通過多態的形式去除了重復、雜亂的if-else語句,這也正是狀態模式的精髓所在。

六、Android實戰中的使用

1、登錄系統,根據用戶是否登錄,判斷事件的處理方式。
2、Wi-Fi管理,在不同的狀態下,WiFi的掃描請求處理不一。

下面以登錄系統為例講解下狀態模式在實戰中的使用:

在android開發中,我們遇到登錄界面是十分常見的,而狀態設計模式在登錄界面的應用十分廣泛,用戶在登錄狀態下和未登錄狀態下,對邏輯的操作是不一樣的。例如最常見的情況就是在玩新浪微博的時候,用戶在登錄的情況下才能完成評論和轉發微博的操作;而當用戶處于未登錄的情況下要執行轉發和評論微博的操作需要進入登錄界面登錄以后才能執行,所以面對這兩者不同的狀況,利用狀態設計模式來設計這個例子最好不過。

1、狀態基類
前面我們講過狀態設計模式的原理實則是多態,在這里我們用UserState接口表示此基類,包換轉發操作和評論這兩種狀態,代碼如下:

public interface UserState {/*** 轉發操作* @param context*/public void forword(Context context);/*** 評論操作* @param context*/public void commit(Context context);
}

2、用戶在登錄和未登錄兩種狀況下的實現類LoginState和LogoutState;代碼如下:

在LoginState.java中,用戶是可以執行轉發和評論操作。

public class LoginState implements UserState{@Overridepublic void forword(Context context) {Toast.makeText(context, "轉發成功", Toast.LENGTH_SHORT).show();}@Overridepublic void commit(Context context) {Toast.makeText(context, "評論成功", Toast.LENGTH_SHORT).show();}   
}

在LogoutState.java中,用戶在未登錄的情況下不允許執行操作,而是應該跳轉到登錄界面執行登錄以后才可以執行。

public class LogoutState implements UserState{/*** 跳轉到登錄界面登錄以后才能轉發*/@Overridepublic void forword(Context context) {gotoLohinActivity(context);}/*** 跳轉到登錄界面登錄以后才能評論*/@Overridepublic void commit(Context context) {gotoLohinActivity(context);}/*** 界面跳轉操作* @param context*/private void gotoLohinActivity(Context context){context.startActivity(new Intent(context,LoginActivity.class));}
}

3、操作角色LoginContext
這里的LoginContext就是在狀態模式的Context角色,是用戶操作對象和管理對象,LoginContext委托相關的操作給狀態對象,在其中狀態的發生改變,LoginContext的行為也發生改變。LoginContext的代碼如*下:
溫馨提示:
這里我們用到單例就是為了全局只有一個LoginContext去控制用戶狀態;

public class LoginContext {//用戶狀態默認為未登錄狀態UserState state = new LogoutState();private LoginContext(){};//私有構造函數,避免外界可以通過new 獲取對象//單例模式public static LoginContext getInstance(){return SingletonHolder.instance;}/***靜態代碼塊*/private static class SingletonHolder{private static final LoginContext instance = new LoginContext();}public void setState(UserState state){this.state = state;}//轉發public void forward(Context context){state.forword(context);}//評論public void commit(Context context){state.commit(context);}
}

4、界面展示
LoginActivity.java,此界面執行登錄操作,登錄成后把 LoginContext.getInstance().setState(new LoginState());設置為登錄狀態,在MainActivity中就執行的是登錄狀態下的操作,即可以轉發可評論;

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;@Overrideprotected 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();}/*** 執行登錄操作* * @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() {@Overridepublic void onSuccess(String t) {if ("200".equals(t)) {//設置為登錄狀態LoginContext.getInstance().setState(new LoginState());startActivity(new Intent(LoginActivity.this,MainActivity.class));finish();Toast.makeText(LoginActivity.this, "登錄成功", Toast.LENGTH_SHORT).show();}}});}@Overridepublic 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,在用戶登錄成功后,點擊轉發和評論執行的是登錄狀態下的操作,而當用戶注銷時,我們把LoginContext的狀態設置為未登錄狀態;LoginContext.getInstance().setState(new LogoutState());此時在點擊轉發和評論操作時就會跳到用戶登錄界面。

public class MainActivity extends Activity {private Button btn_forward;private Button btn_commit;private Button btn_logout;@Overrideprotected 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() {//轉發操作btn_forward.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {//調用LoginContext里面的轉發函數LoginContext.getInstance().forward(MainActivity.this);}});//評論操作btn_commit.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {//調用LoginContext里面的轉發函數LoginContext.getInstance().commit(MainActivity.this);}});//注銷操作btn_logout.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {//設置為注銷狀態LoginContext.getInstance().setState(new LogoutState());}});}
}

七、總結

狀態模式的關鍵點在于不同的狀態下對于同一行為有不同的響應,這其實就是一個將if-else用多態來實現的一個具體示例。在if-else或者switch-case形式下根據不同的狀態進行判斷,如果是狀態A那么執行方法A,狀態B執行方法B,但這種實現使得邏輯耦合在一起,易于出錯,通過狀態模式能夠很好的消除這類”丑陋“的邏輯處理,當然并不是任何出現if-else的地方都應該通過狀態模式重構,模式的運用一定要考慮所處的情景以及你要解決的問題,只有符合特定的場景才建議使用對應的模式。

優點:

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

缺點:

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

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/447018.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/447018.shtml
英文地址,請注明出處:http://en.pswp.cn/news/447018.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Android設計模式之——責任鏈模式

一、介紹 責任鏈模式(Iterator Pattern),是行為型設計模式之一。什么是”鏈“?我們將多個節點首尾相連所構成的模型稱為鏈,比如生活中常見的鎖鏈,就是由一個個圓角長方形的鐵環串起來的結構。對于鏈式結構…

目前基于區塊鏈的檔案防篡改系統的設計如何實現防篡改

架構設計圖 分析 為了保障檔案數據的安全性和隱私性,存儲檔案附件和檔案屬性存儲加密存儲在私有IPFS集群,檔案的IPFS地址和數字指紋存儲在私有區塊鏈上。公有區塊鏈定期存儲和檢查私有區塊鏈最新不可逆區塊的高度和哈希值,以保障私有區塊鏈上…

IPFS的文件存儲模式

IPFS是如何進行文件存儲的 IPFS采用的索引結構是DHT(分布式哈希表),數據結構是MerkleDAG(Merkle有向無環圖) DHT(分布式哈希表) 參考鏈接MerkleDAG(Merkle有向無環圖) 參考鏈接MerkleDAG功能…

Android設計模式之——解釋器模式

一、介紹 解釋器模式(Interpreter Pattern)是一種用的比較少的行為型模式,其提供了一種解釋語言的語法或表達式的方式,該模式定義了一個表達式接口,通過該接口解釋一個特定的上下文。在這么多的設計模式中&#xff0c…

在Docker里面安裝Ubuntu,并且使用ssh進行連接

創建Ubuntu鏡像 1,拉取Ubuntu系統的鏡像 docker pull ubuntu2、查看拉取是否成功 docker images3,運行容器 docker run --name 新建的容器的名字 -ti -v /AAA:/BBB -d -p 3316:22 ubuntu(這個是鏡像的名字)宿主機根目錄中的AAA文件夾就映射到了容器…

Android設計模式之——命令模式

一、介紹 命令模式(Command Pattern),是行為型設計模式之一。命令模式相對于其他的設計模式來說并沒有那么多的條條框框,其實它不是一個很”規范“的模式,不過,就是基于這一點,命令模式相對于其…

C++ 序列化和反序列化學習

定義 程序員在編寫應用程序的時候往往需要將程序的某些數據存儲在內存中,然后將其寫入某個文件或是將它傳輸到網絡中的另一臺計算機上以實現通訊。這些過程將會涉及到程序數據轉化成能被存儲并傳輸的格式,因此被稱為“序列化”(Serializatio…

Android設計模式之——觀察者模式

一、介紹 觀察者模式是一個使用率非常高的模式,它最常用的地方是GUI系統、訂閱——發布系統。因為這個模式的一個重要作用就是解耦,將被觀察者和觀察者解耦,使得它們之間的依賴性更小,甚至做到毫無依賴。以GUI系統來說&#xff0…

Android設計模式之——備忘錄模式

一、介紹 備忘錄模式是一種行為模式,該模式用于保存對象當前狀態,并且在之后可以再次恢復到此狀態,這有點像我們平時說的”后悔藥“。備忘錄模式實現的方式需要保證被保存的對象狀態不能被對象從外部訪問,目的是為了保護好被保存…

c++ memory 頭文件詳細介紹

類 指針特征 pointer_traits (C11) 提供關于指針式類型的信息 (類模板) 垃圾收集器支持 pointer_safety (C11) 列出指針安全模式 (枚舉) 分配器 allocator 默認的分配器 (類模板) allocator_traits (C11) 提供關于分配器類型的信息 (類模板) allocator_arg_t (C11) 標簽類型…

C++ using的三種使用策略以及具體的用法

Using的使用方法 1,命名空間的使用 為了防止代碼沖突,都會使用到命名空間。假設這樣一種情況,當一個班上有兩個名叫 Zara 的學生時,為了明確區分他們,我們在使用名字之外,不得不使用一些額外的信息&#…

Android設計模式之——迭代器模式

一、介紹 迭代器模式(Iterator Pattern)又稱為游標(Cursor)模式,是行為型設計模式之一。迭代器模式算是一個比較古老的設計模式,其源于對容器的訪問,比如Java中的List、Map、數組等&#xff0c…

Android設計模式之——模板方法模式

一、介紹 在面向對象開發過程中,通常會遇到這樣的一個問題,我們知道一個算法所需的關鍵步驟,并確定了這些步驟的執行順序,但是,某些步驟的具體實現是未知的,或者說某些步驟的實現是會隨著環境的變化而改變…

Android設計模式之——訪問者模式

一、介紹 訪問者模式是一種將數據操作與數據結構分離的設計模式,它是《設計模式》中23種設計模式中最復雜的一個,但它的使用頻率并不高,正如《設計模式》的作者GOF對訪問者模式的描述:大多數情況下,你不需要使用訪問者…

C++類模板template <class T>簡單使用方法

一個簡單的例子 兩個數比大小 如果兩個數都是int類型 class Compare_int { public :Compare(int a,int b){xa;yb;}int max( ){return (x>y)?x:y;}int min( ){return (x<y)?x:y;} private :int x,y; }; 如果兩個數是float類型 class Compare_float { public :Compare(…

Android設計模式之——中介者模式

一、介紹 中介者模式&#xff08;Mediator Pattern&#xff09;也稱為調解者模式或調停者模式&#xff0c;Mediator本身就有調停者和調解者的意思。 在日常生活中調停者或調解者這個角色我們見得比較多的是“和事老”&#xff0c;也就是說調解兩個有爭端的人的角色&#xff0…

C++智能指針中unique_ptr部分內容的講解

參考鏈接 std::unique_ptr 介紹 定義位于頭文件<memory>std::unique_ptr 是通過指針占有并管理另一對象&#xff0c;并在 unique_ptr 離開作用域時釋放該對象的智能指針。 在下列兩者之一發生時用關聯的刪除器釋放對象&#xff1a;1&#xff0c;銷毀了管理的 unique_pt…

Java基礎——Java多線程中sleep()、wait()和notify()

一、sleep()sleep()方法源碼&#xff1a;/** * Causes the currently executing thread to sleep (temporarily cease * execution) for the specified number of milliseconds, subject to * the precision and accuracy of system timers and schedulers. The thread * does …

Key_handle的學習

代碼 一切盡在不言中 #pragma once#include "common/common.h" #include "sdf/sdf.h"#include <memory>namespace sdf {namespace algorithm {class KeyHandle {public:using erased_internal_data_t char; //使用erased_internal_data_t等效于ch…

Java基礎——虛擬機結構

一、Java平臺結構圖二、JVM、JRE和JDK關系JVM&#xff1a;Java Virtual Machine&#xff08;Java虛擬機&#xff09;&#xff0c;負責執行符合規范的Class文件 JRE&#xff1a; Java Runtime Environment &#xff08;java運行環境&#xff09;&#xff0c;包含JVM和類庫 JDK&a…