《Android源码设计模式解析与实战》读书笔记(八)
一、状态模式的简介
状态模式中的行为是由状态来决定的,不同的状态下有不同的行为。状态模式和策略模式的结构几乎完全一样,但它们的目的、本质却完全不一样。
状态模式的行为是平行的、不可替换的,策略模式的行为是彼此独立、可相互替换的。
状态模式把对象的行为包装在不同的状态对象里,每一个状态对象都有一个共同的抽象状态基类。
状态模式的意图是让一个对象在其内部状态改变的时候,其行为也随之改变。
1.1、定义
当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类。
1.2、使用场景
- 一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为。
- 代码中包含大量与对象状态有关的条件语句,例如,一个操作中含有庞大的多分支语句,且这些分支依赖于该对象的状态。
状态模式将每一个条件分支放入一个独立的类中,这使得我们可以根据对象自身的情况将对象的状态作为一个对象,这一对象可以不依赖于其他对象而独立变化,通过多态来去除过多的、重复的if-else等分支语句。
二、状态模式的简单示例
下面以电视遥控器为例来演示一下状态模式的实现。
第一版代码:
/** * 电视遥控器,含有开机、关机、下一频道、上一频道、调高音量、调低音量这几个功能 */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,代码重复、相对较为混乱。
状态模式即使为解决这类的问题而出现的,如下代码:
//电视状态接口,定义了电视操作的函数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();复制代码
输出结果:
三、状态模式实战
在开发过程中,用到状态模式最常见的地方应该是用户登录系统。在用户已登录和未登录的情况下,对于同一事件的处理行为是不一样的。
用户的默认状态为未登录状态,此时用户在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); }}复制代码
四、总结
状态模式的关键点在于不同的状态下对于同一行为有不同的响应,这其实就是一个将if-else用多态来实现的一个具体示例。
4.1、优点
状态模式将所有与一个特定的状态相关的行为都放入一个状态对象中,它提供了一个更好的方法来组织与特定状态的相关代码,将繁琐的状态判断转换为结构清晰的状态类族,在避免代码膨胀的同时也保证了可扩展性与可维护性。
4.2、缺点
状态模式的使用必然会增加系统类和对象的个数。