随时灵活变化-状态模式

模式介绍

状态模式的结构和策略模式几乎一模一样。

但是它们两者的目的、本质却完全不同。

策略模式的行为是彼此独立,相互替换的。回想之前举的价格计算器,我们出行时想使用地铁,则使用地铁的价格计算器,如果使用出租车,则使用出租车的价格计算器……

状态模式的行为则是平行的,不可替换的。状态模式更像是被封装在对象内部的一个东西,当对象的状态发生变化时,其行为也要发生变化。

使用场景

  • 一个对象的行为需要根据状态发生变化,尤其是在运行时状态发生变化,行为需要跟着一起变化时;
  • 代码中有大量判断语句(if、switch),且这些语句依赖该对象的状态。

模式角色

  • State:状态接口,定义所有行为的抽象。
  • ConcreteStateA、ConcreteStateB:某个状态的具体行为实现,一个状态对应一个具体行为实现。

模式示例

相信大家在开发中,只要是动态App(和服务器有交互),都会有登录的需求。

针对这个需求,我们可以得出两个状态:登录状态、未登录状态。

接着我们可以想象一些行为:

  • 登录
  • 退出登录
  • 查看账户金币
  • 赚取金币
  • 金币兑换道具

针对上述行为,在登录状态和未登录状态下,所做的事情是完全不同的:

  1. 登录状态:
    • 登录:提示用户已经登录过了,请勿重复登录
    • 退出登录:清除用户缓存并提示用户退出登录成功
    • 查看账户金币:从服务器查询用户余额
    • 赚取金币:跳转到赚金币页面
    • 金币兑换道具:跳转到兑换道具页
  2. 未登录状态:
    • 登录:判断用户输入的账号密码,正确提示用户登录成功。
    • 退出登录:提示用户已经退出过了。
    • 查看账户金币:提示用户请先登录
    • 赚取金币:跳转到赚金币页(这里也可以提示用户登录,具体看产品定的登录时机)
    • 金币兑换道具:提示用户请先登录

想必看完这个示例,大家已经对状态模式有了一些自己的见解。

这种情况下,非常适合使用状态模式

如果项目中,还是通过判断语句来进行状态的区分,就说明这个项目没有很好地应用状态模式

下面我们就来将上述案例转化成代码。

首先是状态行为的抽象,该抽象包含了所有的行为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public interface UserState {

//登录
void login();

//登出
void logout();

//查询余额
void seeMoney();

//赚金币
void earnMoney();

//兑换道具
void exchange();
}

针对我们上面描述的示例,有以上5种行为,我们将其进行了抽象。

接着来思考行为的具体实现:登录状态的具体实现、未登录状态的具体实现。

具体做起来也很简单,就是创建两个类去实现我们的行为抽象,在各自的实现下,去实现该状态下,应该做的事情。

首先来看登录状态:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class LoginStateImpl implements UserState {
@Override
public void login() {
Toast.makeText(App.context, "您已经登录了,无需登录!", Toast.LENGTH_SHORT).show();
}

@Override
public void logout() {

Toast.makeText(App.context, "退出登录成功!", Toast.LENGTH_SHORT).show();
}

@Override
public void seeMoney() {
Toast.makeText(App.context, "您目前有100金币", Toast.LENGTH_SHORT).show();
}

@Override
public void earnMoney() {
Toast.makeText(App.context, "跳转到-赚金币", Toast.LENGTH_SHORT).show();
}

@Override
public void exchange() {
Toast.makeText(App.context, "跳转到-兑换道具", Toast.LENGTH_SHORT).show();
}
}

根据示例需求,我实现了登录状态下,这5种行为的具体实现。

未登录状态也是同理,这里就不列举代码了,可以直接查看GitHub

接下来我们来看一下测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//默认是未登录状态
private UserState userState = new LogutStateImpl();

@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.bt_login://登录行为
userState.login();
//核心:更换用户状态
userState = new LoginStateImpl();
break;
case R.id.bt_logout://退出登录行为
userState.logout();
userState = new LogutStateImpl();
break;
case R.id.bt_see_money://查看余额行为
userState.seeMoney();
break;
case R.id.bt_earn_money://赚取金币行为
userState.earnMoney();
break;
case R.id.bt_exchange://兑换行为
userState.exchange();
break;
}
}

这里也是利用多态的特性,根据状态的变化,注入不同的实现。

至此,我们已经使用状态模式成功实现了上述需求,并且去除了重复、杂乱的判断语句,体现出了状态模式的精髓。

总结

代码已经上传至GitHub,可以下载查阅。

其实看到这里,各位看官想必已经了解了状态模式。

状态模式的关键点在于:不同状态下、同一行为的不同响应。

状态模式是为了优化代码结构而产生的。我们通过if-else其实可以完美判断用户的登录状态,但是这种实现使得逻辑与具体行为耦合在一起,后期难以维护。

状态模式就是为了消除这种丑态,实现逻辑与行为的解耦。

当然并不是所有的if-else都适合使用状态模式,具体是否使用,还是由你来决定。