本文实例讲述了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{ @OverrIDe public voID nextChannel() { } @OverrIDe public voID prevChannel() { } @OverrIDe public voID turnUp() { } @OverrIDe 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 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("开机了"); } @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(); }}
调用:
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{ @OverrIDe public voID forword(Context context) { Toast.makeText(context,"转发成功",Toast.LENGTH_SHORT).show(); } @OverrIDe public voID commit(Context context) { Toast.makeText(context,"评论成功",Toast.LENGTH_SHORT).show(); }}
在logoutState.java中,用户在未登录的情况下不允许执行 *** 作,而是应该跳转到登录界面执行登录以后才可以执行。
public class logoutState implements UserState{ /** * 跳转到登录界面登录以后才能转发 */ @OverrIDe public voID forword(Context context) { gotolohinActivity(context); } /** * 跳转到登录界面登录以后才能评论 */ @OverrIDe public 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; @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(); } /** * 执行登录 *** 作 * * @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)) { //设置为登录状态 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,在用户登录成功后,点击转发和评论执行的是登录状态下的 *** 作,而当用户注销时,我们把LoginContext的状态设置为未登录状态;LoginContext.getInstance().setState(new logoutState());
此时在点击转发和评论 *** 作时就会跳到用户登录界面。
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() { //转发 *** 作 btn_forward.setonClickListener(new OnClickListener() { @OverrIDe public voID onClick(VIEw v) { //调用LoginContext里面的转发函数 LoginContext.getInstance().forward(MainActivity.this); } }); //评论 *** 作 btn_commit.setonClickListener(new OnClickListener() { @OverrIDe public voID onClick(VIEw v) { //调用LoginContext里面的转发函数 LoginContext.getInstance().commit(MainActivity.this); } }); //注销 *** 作 btn_logout.setonClickListener(new OnClickListener() { @OverrIDe public voID onClick(VIEw v) { //设置为注销状态 LoginContext.getInstance().setState(new logoutState()); } }); }}
七、总结
状态模式的关键点在于不同的状态下对于同一行为有不同的响应,这其实就是一个将if-else用多态来实现的一个具体示例。在if-else或者switch-case形式下根据不同的状态进行判断,如果是状态A那么执行方法A,状态B执行方法B,但这种实现使得逻辑耦合在一起,易于出错,通过状态模式能够很好的消除这类”丑陋“的逻辑处理,当然并不是任何出现if-else的地方都应该通过状态模式重构,模式的运用一定要考虑所处的情景以及你要解决的问题,只有符合特定的场景才建议使用对应的模式。
优点:
将所有与一个特定的状态相关的行为都放入一个状态对象中,它提供了一个更好的方法来组织与特定状态相关的代码,将繁琐的状态判断转换成结构清晰的状态类族,在避免代码膨胀的同时也保证了可扩展性与可维护性。
缺点:
状态模式的使用必然会增加系统类和对象的个数。
更多关于AndroID相关内容感兴趣的读者可查看本站专题:《Android开发入门与进阶教程》、《Android调试技巧与常见问题解决方法汇总》、《Android基本组件用法总结》、《Android视图View技巧总结》、《Android布局layout技巧总结》及《Android控件用法总结》
希望本文所述对大家AndroID程序设计有所帮助。
总结以上是内存溢出为你收集整理的Android编程设计模式之状态模式详解全部内容,希望文章能够帮你解决Android编程设计模式之状态模式详解所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)