设计模式学习笔记(二十三):状态模式
1 概述
1.1 引言
状态模式用于解决系统中复杂对象的状态转换以及不同状态下行为的封装问题。当系统中的某个对象存在多个状态,这些状态之间可以进行转换,而且对象在不同状态下行为不相同时可以使用状态模式。
状态模式将一个对象的状态从该对象中分离出来,封装到专门的状态类中,使得对象状态可以灵活变化,对于客户端而言,无须关心对象状态的转换以及对象所处的当前状态,无论对于何处状态对象,客户端都可以一致性地处理。
1.2 定义
状态模式:允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎是修改了它的类。
状态模式是一种对象行为型模式。
1.3 结构图
1.4 角色
Context
(环境类):环境类角色又称上下文类,是拥有多种状态的对象,环境类的状态存在多样性且在不同状态下对象的行为有所不同。在环境类中维护一个抽象状态类State
的实例,定义当前状态,实现时将具体状态类注入其中State
(抽象状态类):声明各种不同状态对应的方法,封装具体状态类的共同方法ConcreteState
(具体状态类):抽象状态类的子类,实现其中的具体状态行为方法
2 典型实现
2.1 步骤
- 定义环境类:环境类包含一个抽象状态成员,可以通过构造方法或者setter注入具体状态,同时包含业务方法,该业务方法会调用抽象状态类的处理状态方法
- 定义抽象状态类:声明状态处理方法,使用环境类作为参数
- 定义具体状态类:实现/继承抽象状态类,按实际需要实现其中的状态处理方法
2.2 环境类
class Context
{
private State state = new ConcreteState1();
public void setState(State state)
{
this.state = state;
}
public void request()
{
state.handle(this);
}
}
具有默认初始化状态,可以通过setter修改状态,request
为环境类业务方法,其中调用了抽象状态类的状态处理方法。
2.3 抽象状态类
interface State
{
void handle(Context context);
}
这里设计为接口,使用环境类作为参数。
2.4 具体状态类
class ConcreteState1 implements State
{
@Override
public void handle(Context context)
{
System.out.println("具体状态1方法");
context.setState(new ConcreteState2());
}
}
class ConcreteState2 implements State
{
@Override
public void handle(Context context)
{
System.out.println("具体状态2方法");
context.setState(new ConcreteState1());
}
}
两个具体状态类,执行完具体状态类里面的代码后,通过环境类参数将环境类切换到另一个状态。
2.5 客户端
public static void main(String[] args)
{
Context context = new Context();
context.request();
context.request();
}
客户端直接调用环境类业务方法,输出如下:
3 实例
设计一个银行账户,账户允许不同的状态,余额大于0处于正常状态,余额大于-2000小于0处于透支状态,余额小于等于-2000处理受限状态。根据余额不同状态可发生转换,使用状态模式进行设计。
设计如下:
- 环境类:
Account
- 抽象状态类:
State
- 具体状态类:
NormalState
+OverdraftState
+RestrictedState
首先是环境类:
class Account
{
private State state = new NormalState(this);
private double balance = 0.0;
public void setState(State state)
{
this.state = state;
}
public void deposit(double amount)
{
balance += amount;
state.check();
}
public void withdraw(double amount)
{
balance -= amount;
state.check();
}
public double getBalance()
{
return balance;
}
@Override
public String toString()
{
return "当前状态:"+state;
}
}
初始化为正常状态,余额为0,在存款/取款中调用抽象状态类的状态检查方法,也就是状态转换交由状态类处理。
接着是抽象状态类:
abstract class State
{
protected Account account;
public abstract void check();
}
拥有一个环境类的引用,以便使用环境类的setState
改变状态方法。
最后是具体状态类:
class NormalState extends State
{
public NormalState(Account account)
{
this.account = account;
}
@Override
public void check()
{
if(account.getBalance() >= -2000 && account.getBalance() < 0)
account.setState(new OverdraftState(account));
else if(account.getBalance() < -2000)
account.setState(new RestrictedState(account));
}
@Override
public String toString()
{
return "正常状态";
}
}
class OverdraftState extends State
{
public OverdraftState(Account account)
{
this.account = account;
}
@Override
public void check()
{
if(account.getBalance() >= 0)
account.setState(new NormalState(account));
else if(account.getBalance() < -2000)
account.setState(new RestrictedState(account));
}
@Override
public String toString()
{
return "透支状态";
}
}
class RestrictedState extends State
{
public RestrictedState(Account account)
{
this.account = account;
}
@Override
public void check()
{
if(account.getBalance() >= 0)
account.setState(new NormalState(account));
else if(account.getBalance() >= -2000 && account.getBalance() < 0)
account.setState(new OverdraftState(account));
}
@Override
public String toString()
{
return "受限状态";
}
}
三个具体状态类分别表示三种不同状态,在其中的check
方法中进行状态检查以及切换状态。
测试:
public static void main(String[] args)
{
Account account = new Account();
account.deposit(1000);
System.out.println(account);
account.withdraw(2000);
System.out.println(account);
account.withdraw(2000);
System.out.println(account);
account.deposit(10000);
System.out.println(account);
}
客户端不需要理会具体状态,按正常流程操作即可,下面是输出:
4 共享状态
有时候多个环境对象可能需要共享同一个状态,这时需要将状态对象定义为环境类的静态成员,例子如下:
某个系统要求两个开关对象必须处于一样的状态,要么同时关,要么同时开,开关可以自由切换状态,使用状态模式进行设计。
设计如下:
- 环境类:
Switch
- 抽象状态类:
State
- 具体状态类:
OffState
+OnState
环境类:
class Switch
{
private State state;
private static final State onState;
private static final State offState;
private String name;
static
{
onState = new OnState();
offState = new OffState();
}
public Switch(String name)
{
this.name = name;
this.state = offState;
}
public String getName()
{
return name;
}
public void setState(State state)
{
this.state = state;
}
public static State getOnState()
{
return onState;
}
public static State getOffState()
{
return offState;
}
public void on()
{
state.on(this);
}
public void off()
{
state.off(this);
}
}
环境类初始化两个静态的开与关状态,在on
以及off
中调用状态方法。
抽象状态:
interface State
{
void on(Switch s);
void off(Switch s);
}
具体状态:
class OnState implements State
{
@Override
public void on(Switch s)
{
System.out.println("开关"+s.getName()+"已经打开");
}
@Override
public void off(Switch s)
{
System.out.println("开关"+s.getName()+"关闭");
s.setState(Switch.getOffState());
}
}
class OffState implements State
{
@Override
public void on(Switch s)
{
System.out.println("开关"+s.getName()+"开启");
s.setState(Switch.getOnState());
}
@Override
public void off(Switch s)
{
System.out.println("开关"+s.getName()+"已经关闭");
}
}
实现抽象状态的开关方法,处于关状态时调用on
会切换到开状态,调用off
则不处理,处于开状态同理。
测试:
public static void main(String[] args)
{
Switch a = new Switch("A");
Switch b = new Switch("B");
a.on();
b.on();
a.off();
b.off();
a.on();
b.on();
}
输出:
5 环境类实现状态切换
在上面的例子中,都是通过具体状态类进行状态切换,比如:
class RestrictedState extends State
{
//...
@Override
public void check()
{
if(account.getBalance() >= 0)
account.setState(new NormalState(account));
else if(account.getBalance() >= -2000 && account.getBalance() < 0)
account.setState(new OverdraftState(account));
}
}
class OnState implements State
{
//...
@Override
public void off(Switch s)
{
System.out.println("开关"+s.getName()+"关闭");
s.setState(Switch.getOffState());
}
}
状态切换也可以由环境类进行统一处理,但是如果增加新的状态类可能需要修改环境类代码。例子如下:
设计一个放大镜工具,单击一次放大一倍,单击两次再放大一杯,第三次就恢复默认大小,使用状态模式进行设计。
设计如下:
- 环境类:
Screen
- 抽象状态类:
State
- 具体状态类:
NormalState
+LargerState
+LargestState
环境类:
class Screen
{
private State state;
private static final State normalState;
private static final State largerState;
private static final State largestState;
static
{
normalState = new NormalState();
largerState = new LargerState();
largestState = new LargestState();
}
public Screen()
{
this.state = normalState;
}
public void onClick()
{
if(state == normalState)
state = largerState;
else if(state == largerState)
state = largestState;
else
state = normalState;
state.display();
}
}
首先初始化各个状态,然后在onClick()
中由环境类控制状态切换。
状态类:
interface State
{
void display();
}
class NormalState implements State
{
@Override
public void display()
{
System.out.println("正常大小");
}
}
class LargerState implements State
{
@Override
public void display()
{
System.out.println("两倍大小");
}
}
class LargestState implements State
{
@Override
public void display()
{
System.out.println("四倍大小");
}
}
测试:
public static void main(String[] args)
{
Screen screen = new Screen();
screen.onClick();
screen.onClick();
screen.onClick();
}
输出如下:
6 主要优点
- 封装转换规则:状态模式中可以将状态的转换代码封装在环境类或具体状态类中,可以将状态转换代码进行集中管理,而不是分散在一个个业务方法中
- 通过注入状态修改行为:将所有与某个状态有关的行为放到一个类中,只需要注入一个不同的状态对象即可使环境拥有不同的行为
- 状态转换逻辑与状态对象一体化:允许状态转换逻辑与状态对象合成一体,而不是提供一个巨大的条件语句块,状态模式可以避免使用庞大的条件语句来将业务方法和状态转换代码交织在一起
7 主要缺点
- 增大运行开销:使用状态模式会增加系统中类和对象的个数
- 实现复杂:状态模式的程序结构实现复杂,如果使用不当将导致程序结构和代码混乱,增加系统设计难度
- 对OCP支持不好:增加新的状态类需要修改那些负责状态转换的源代码,否则无法转换到新增状态,而且修改某个状态类的行为也需要修改对应的源代码
8 适用场景
- 对象的行为依赖于它的状态,状态的改变会导致行为的变化
- 在代码中包含大量与对象状态有关的条件语句,这些条件语句的出现会导致代码可维护性和灵活性变差,不能方便地增加和删除状态,并且导致客户类与类库之间的耦合度增强