随遇而安——状态模式
状态模式的定义
当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类。状态模式和策略模式的结构几乎一样,但是他们的目的、本质却完全不一样。状态模式的行为是平行的、不可替换的,策略模式的行为是彼此独立、可相互替换的。状态模式的意图是让一个对象在其内部状态改变的时候,其行为也随之改变。
状态模式的使用场景
- 一个对象的行为取决于它的状态,并且它必须在运行时根据状态改变它的行为。
- 代码中包含大量与对象状态有关的条件语句,例如,一个操作中含有庞大的多分支语句,且这些分支依赖该对象状态。
状态模式的实例
下面我们来看下第一个实例:电视遥控器
电视的状态简单分为开机状态和关机状态,在开机状态下可以通过遥控器进行频道切换、音量控制等,重复按开机键是无效的;而在关机状态下,频道切换、调整音量、关机都是无效的操作,只有按开机时会生效。我们先来看看一般的写法。
TvController.class
/**
* description: 电视遥控器,含有开机、关机、下一个频道、上一个频道、调高音量、调低音量这几个功能
*/
public class TvController {
private static final String TAG = "TvController";
//开机状态
private final static int POWER_ON = 1;
//关机状态
private final static int POWER_OFF = 2;
private int state = POWER_OFF;
public void powerOn() {
if (state == POWER_OFF) {
Log.i(TAG, "开机啦");
}
state = POWER_ON;
}
public void powerOff() {
if (state == POWER_ON) {
Log.i(TAG, "关机啦");
}
state = POWER_OFF;
}
public void nextChannel() {
if (state == POWER_ON) {
Log.i(TAG, "下一个频道");
} else {
Log.i(TAG, "没有开机");
}
}
public void prevChannel() {
if (state == POWER_ON) {
Log.i(TAG, "上一个频道");
} else {
Log.i(TAG, "没有开机");
}
}
public void turnUp() {
if (state == POWER_ON) {
Log.i(TAG, "调高音量");
} else {
Log.i(TAG, "没有开机");
}
}
public void turnDown() {
if (state == POWER_ON) {
Log.i(TAG, "调低音量");
} else {
Log.i(TAG, "没有开机");
}
}
}
可以看到,在TvController类中,state字段存储了电视的状态,并且在各个操作中根据状态来判断是否应该执行,这就导致了在每个功能中都需要使用if-else。如果状态变为5个,功能变为10个,那就会晕死在判断里吧。那么接下来用状态模式来重构一下。
状态接口
TvState.class
public interface TvState {
public void nextChannel();
public void prevChannel();
public void turnUp();
public void turnDown();
}
具体状态类
PowerOffState.class
/**
* description: 关机状态,此时只有开机功能是有效的
*/
public class PowerOffState implements TvState {
@Override
public void nextChannel() {
}
@Override
public void prevChannel() {
}
@Override
public void turnUp() {
}
@Override
public void turnDown() {
}
}
PowerOnState.class
/**
* description: 开机状态,此时再触发开机功能不做任何操作
*/
public class PowerOnState implements TvState {
private static final String TAG = "PowerOnState";
@Override
public void nextChannel() {
Log.i(TAG, "下一个频道");
}
@Override
public void prevChannel() {
Log.i(TAG, "上一个频道");
}
@Override
public void turnUp() {
Log.i(TAG, "调高音量");
}
@Override
public void turnDown() {
Log.i(TAG, "调低音量");
}
}
电源操作接口
PowerController.class
public interface PowerController {
public void powerOn();
public void powerOff();
}
操作接口,提供给外部调用
TvController.class
public class TvController implements PowerController{
private static final String TAG = "TvController";
TvState tvState;
public void setTvState(TvState tvState) {
this.tvState = tvState;
}
@Override
public void powerOn() {
setTvState(new PowerOnState());
Log.i(TAG, "开机啦");
}
@Override
public void powerOff() {
setTvState(new PowerOffState());
Log.i(TAG, "关机啦");
}
public void nextChannel() {
tvState.nextChannel();
}
public void prevChannel() {
tvState.prevChannel();
}
public void turnUp() {
tvState.turnUp();
}
public void turnDown() {
tvState.turnDown();
}
}
用户调用
public class Client {
public void test() {
TvController tvController = new TvController();
tvController.powerOn();
tvController.nextChannel();
tvController.turnUp();
tvController.powerOff();
tvController.turnUp();
}
}
Log日志如下
09-30 23:58:57.248 24287-24287/? I/TvController: 开机啦
09-30 23:58:57.248 24287-24287/? I/PowerOnState: 下一个频道
09-30 23:58:57.248 24287-24287/? I/PowerOnState: 调高音量
09-30 23:58:57.248 24287-24287/? I/TvController: 关机啦
状态模式将这些行为封装到状态类中,在进行操作时将这些功能转发给状态对象,不同的状态有不同的实现,这样就通过了多态的形式去除了重复、杂乱的if-else语句。
接下来再看一个实例
在Android中最常用的状态模式的地方,应当属登陆这一块了。有些app会设计成不登陆也可以进入app使用,但是涉及到使用用户信息的时候就需要先登陆,那这些需要用户信息的地方(比如转发、评论),如果通过用户是否登陆来一个个判断的话,将出现很多if-else,所以这里可以使用状态模式。
假设MainActivity是app的首页,里面有转发、评论和注销的功能,不登陆也可以直接进入到首页,当点击评论或者转发时,没有登录则直接跳转到登陆,登陆了则直接评论或者转发。
用户状态接口
UserState.class
public interface UserState {
/**
* description: 转发
*/
public void forward(Context context);
/**
* description: 评论
*/
public void comment(Context context);
}
具体状态类
LoginedState.class
/**
* description: 已登录状态
*/
public class LoginedState implements UserState {
private static final String TAG = "LoginedState";
@Override
public void forward(Context context) {
Log.i(TAG, "转发微博");
}
@Override
public void comment(Context context) {
Log.i(TAG, "评论微博");
}
}
LogoutState.class
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);
}
}
向外界提供调用Context类
LoginContext.class
public class LoginContext {
private UserState state = new LogoutState();
private LoginContext() { }
public static LoginContext getSingleton() {
return LoginContext.LoginContextHolder.loginContext;
}
private static class LoginContextHolder {
private static final LoginContext loginContext = new LoginContext();
}
public void setState(UserState state) {
this.state = state;
}
//转发
public void forward(Context context) {
state.forward(context);
}
//评论
public void comment(Context context) {
state.comment(context);
}
}
接下来就是具体的逻辑调用了
MainActivity.class
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 view) {
//调用LoginContext的转发函数
LoginContext.getSingleton().forward(MainActivity.this);
}
});
//评论按钮
findViewById(R.id.comment_btn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//调用LoginContext的评论函数
LoginContext.getSingleton().comment(MainActivity.this);
}
});
//注销按钮
findViewById(R.id.logout_btn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//设置为注销状态
LoginContext.getSingleton().setState(new LogoutState());
}
});
}
}
LoginActivity.class
public class LoginActivity extends AppCompatActivity {
private static final String TAG = "LoginActivity";
private EditText userNameEditText;
private EditText pwdEditText;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
userNameEditText = (EditText) findViewById(R.id.user_name_edit_text);
pwdEditText = (EditText) findViewById(R.id.pwd_edit_text);
findViewById(R.id.login_btn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
login();
finish();
}
});
}
private void login() {
String userName = userNameEditText.getText().toString().trim();
String pwd = pwdEditText.getText().toString().trim();
//执行网络请求,进行登陆...
//登陆成功后修改为已登录状态
LoginContext.getSingleton().setState(new LoginedState());
Log.i(TAG, "登录成功");
}
}
小结
优点
状态模式将与一个特定的状态相关的行为都放入一个状态对象中,它提供了一个更好的方法来组织与特定状态相关的代码,将烦琐的状态判断转换成结构清晰的状态类族,在避免代码膨胀的同时,也可以保证了可扩展性与可维护性。
缺点
状态模式的使用必然会增加系统类和对象类的个数。