水,可以凝固成冰,也可以受热蒸发成水蒸气。水可以流动,冰可以雕刻,蒸汽可以扩散。生活中,很多事物都有多种状态。在软件系统中,通常可以使用复杂的条件判断语句来进行状态的判断和转换操作,这会导致代码的可维护性和灵活性下降,特别是在出现新的状态时,代码的扩展性很差,客户端代码也需要进行响应的修改,违反了开闭原则。为了解决这个问题,并使客户端代码与对象状态之间的耦合度降低,状态模式出现了。
状态模式用于解决系统中复杂对象的状态转换以及不同状态下行为的封装问题。当系统中的某个对象存在多个状态,这些状态之间可以进行转换,而且对象在不同状态下会有不同的行为,此时就可以使用状态模式。状态模式将一个对象的状态从该对象中分离出来,封装到专门的状态类中,使得对象状态可以灵活变化。对于客户端来说,无需关心对象状态的转换以及对象所处的当前状态,无论对于何种状态,客户端都可以一致处理。该模式又名状态对象,是一种对象行为型模式。
定义
允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎改变了它的类。
结构
Context(环境类)
又称为上下文类,它是拥有多种状态的对象。由于环境类的状态存在多样性且在不同状态下对象的行为有所不同,因此将状态独立出去形成单独的状态类。在环境类中维护一个抽象状态类State的实例,这个实例定义当前状态,在具体实现时,它是一个State的子类对象。
State(抽象状态类)
它用于定义一个接口以封装与环境类的一个特定状态相关的行为,在抽象状态类中声明了各种不同状态对应的方法,而在其子类中实现了这些方法,由于不同状态下对象的行为可能不同,因此在不同子类中方法的实现可能存在不同,相同的方法可以写在抽象状态类中。
ConcreteState(具体状态类)
它是抽象状态类的子类,每一个子类实现一个与环境类的一个状态相关的行为,每一个具体状态类对应环境的一个具体状态,不同的具体状态类的行为有所不同。
举例说明
银行存取钱的问题,如果余额大于-2000可以取钱,但是余额小于0需要支付利息。余额小于0为透支状态,等于2000为受限状态。
1.环境类 - 银行类
// 环境类 - 银行账户
public class Account {
// 维持一个对抽象状态对象的引用
private AccountState state;
// 开户名
private String owner;
// 账户余额
private double balance = 0;
public Account(String owner, double balance) {
this.owner = owner;
this.balance = balance;
// 设置初始状态
this.state = new NormalState(this);
System.out.println(this.owner + "开户,初始金额为" + balance);
System.out.println("------------------------------------------");
}
public void setState(AccountState state) {
this.state = state;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
public void deposit(double amount) {
System.out.println(this.owner + "存款" + amount);
// 调用状态对象的deposit()方法
state.deposit(amount);
System.out.println("现在余额为" + this.balance);
System.out.println("现在账户状态为" + this.state.getClass().getSimpleName());
System.out.println("------------------------------------------");
}
public void withdraw(double amount) {
System.out.println(this.owner + "取款" + amount);
// 调用状态对象的withdraw()方法
state.withdraw(amount);
System.out.println("现在余额为" + this.balance);
System.out.println("现在账户状态为" + this.state.getClass().getSimpleName());
System.out.println("------------------------------------------");
}
public void computeInterest() {
state.computeInterest();
}
}
2.抽象状态类
// 抽象状态类
public abstract class AccountState {
// 对环境类的引用,可能状态对象中需要用到环境类中的某些属性
protected Account acc;
// 存款
public abstract void deposit(double amount);
// 取款
public abstract void withdraw(double amount);
// 计算利息
public abstract void computeInterest();
// 状态转换
public abstract void stateCheck(double oldBalance);
}
3.具体状态类
//具体状态类 - 正常状态
public class NormalState extends AccountState{
public NormalState(Account acc) {
this.acc = acc;
}
public NormalState(AccountState state) {
this.acc = state.acc;
}
@Override
public void deposit(double amount) {
double oldBalance = acc.getBalance();
acc.setBalance(oldBalance + amount);
stateCheck(oldBalance);
}
@Override
public void withdraw(double amount) {
double oldBalance = acc.getBalance();
acc.setBalance(oldBalance - amount);
stateCheck(oldBalance);
}
@Override
public void computeInterest() {
System.out.println("正常状态,无需支付利息!");
}
@Override
public void stateCheck(double oldBalance) {
if(acc.getBalance() > -2000 && acc.getBalance() <= 0) {
acc.setState(new OverdraftState(this));
}else if(acc.getBalance() == -2000) {
acc.setState(new RestrictedState(this));
}else if(acc.getBalance() < -2000) {
// 当前取出
acc.setBalance(oldBalance);
System.out.println("操作受限!当前不允许取出该数值的钱!");
}
}
}
// 具体状态类 - 透支状态
public class OverdraftState extends AccountState{
public OverdraftState(AccountState state) {
this.acc = state.acc;
}
@Override
public void deposit(double amount) {
double oldBalance = acc.getBalance();
acc.setBalance(oldBalance + amount);
stateCheck(oldBalance);
}
@Override
public void withdraw(double amount) {
double oldBalance = acc.getBalance();
acc.setBalance(oldBalance - amount);
stateCheck(oldBalance);
}
@Override
public void computeInterest() {
System.out.println("计算利息!");
}
@Override
public void stateCheck(double oldBalance) {
if(acc.getBalance() > 0) {
acc.setState(new NormalState(this));
}else if(acc.getBalance() == -2000) {
acc.setState(new RestrictedState(this));
}else if(acc.getBalance() < -2000) {
// 当前取出
acc.setBalance(oldBalance);
System.out.println("操作受限!当前不允许取出该数值的钱!");
}
}
}
//具体状态类 - 受限状态
public class RestrictedState extends AccountState{
public RestrictedState(AccountState state) {
this.acc = state.acc;
}
@Override
public void deposit(double amount) {
double oldBalance = acc.getBalance();
acc.setBalance(oldBalance+ amount);
stateCheck(oldBalance);
}
@Override
public void withdraw(double amount) {
System.out.println("账号受限,取款失败!");
}
@Override
public void computeInterest() {
System.out.println("计算利息!");
}
@Override
public void stateCheck(double oldBalance) {
if(acc.getBalance() > 0) {
acc.setState(new NormalState(this));
}else if(acc.getBalance() > -2000) {
acc.setState(new OverdraftState(this));
}
}
}
4.测试
public class Client {
public static void main(String[] args) {
Account acc = new Account("张三", 0.0);
acc.deposit(1000);
// 尝试取2W
acc.withdraw(20000);
acc.withdraw(2000);
acc.deposit(3000);
acc.withdraw(4000);
acc.withdraw(1000);
acc.computeInterest();
}
}
5.运行截图
共享状态
在有些情况下,多个环境对象可能需要共享同一个状态,如果希望在系统中实现多个环境对象共享一个或多个状态对象,那么需要将这些状态对象定义为环境类的静态成员对象。
举例说明
某系统需要两个开关处于同一个状态,即开关1是开启状态,那么开关2也是开启状态;开关2是关闭状态,那么观看1也是关闭状态。也就是说在使用它们的时候,它们状态必须一致。
1.环境类
// 环境类 - 开关
public class Switch {
// 静态状态对象 - 当前状态,开启状态,关闭状态
private static SwitchState currentState,onState,offState;
private String name;
public Switch(String name) {
this.name = name;
onState = new OnState();
offState = new OffState();
// 默认当前是开启状态
currentState = onState;
}
public void setSwitchState(SwitchState state) {
currentState = state;
}
public static SwitchState getState(String type) {
if(type.equalsIgnoreCase("on")) {
return onState;
}else {
return offState;
}
}
public void off() {
System.out.print(name);
currentState.off(this);
}
public void on() {
System.out.print(name);
currentState.on(this);
}
}
2.抽象状态类
// 抽象状态类 - 开关状态类
public abstract class SwitchState {
// 开
public abstract void on(Switch s);
// 关
public abstract void off(Switch s);
}
3.具体状态类
// 具体状态类 - 开启状态
public class OnState extends SwitchState{
@Override
public void on(Switch s) {
System.out.println("已经打开!");
}
@Override
public void off(Switch s) {
s.setSwitchState(Switch.getState("off"));
System.out.println("关闭!");
}
}
// 具体状态类 - 关闭状态
public class OffState extends SwitchState{
@Override
public void on(Switch s) {
s.setSwitchState(Switch.getState("on"));
System.out.println("打开!");
}
@Override
public void off(Switch s) {
System.out.println("已经关闭!");
}
}
4.测试
public class Client {
public static void main(String[] args) {
Switch s1 = new Switch("开关1");
Switch s2 = new Switch("开关2");
s1.on();
s2.on();
s1.off();
s2.off();
s1.on();
s2.on();
}
}
5.运行截图
它是怎么做到共享状态的呢?主要是利用里static关键字的特性,将环境类中的状态设置为static,这个状态就是属于类的,而不是对象的,所以任何对象的状态是一致的。
使用环境类实现状态转换
在实现状态转换的时候,具体状态类可以通过调用环境类Context的setState()方法来进行状态转换,也可以统一由环境类来实现状态。此时,如果增加新的状态,可能需要修改其他状态类或环境类的源代码,否则系统无法转换到新增的状态。但是对于客户端来说,它不需要关心状态类,可以为环境类设置默认的状态类,而将状态转换的工作交给具体状态类或者环境类来完成,具体的转换细节对于客户端来说是透明的。
上诉“银行系统”采用的是通过具体状态类来实现状态转换,即stateCheck方法,该方法内部实现了状态的转换。除此之外,还可以通过环境类来实现状态转换,环境类作为状态管理器,统一实现各种状态之间的转换操作。
举例说明
某屏幕放大镜,点击1次放大一倍,再点击1次再放大一倍,再点击1次则还原到默认大小。
1.抽象状态类
// 抽象状态类 - 屏幕状态类
public abstract class ScreenState {
// 放大/缩小
public abstract void display();
}
2.具体状态类
// 具体状态类 - 正常放大
public class NormalState extends ScreenState{
@Override
public void display() {
System.out.println("正常大小!");
}
}
// 具体状态类 - 二倍放大
public class TwoState extends ScreenState{
@Override
public void display() {
System.out.println("二倍放大!");
}
}
// 具体状态类 - 四倍放大
public class FourState extends ScreenState{
@Override
public void display() {
System.out.println("四倍放大!");
}
}
3.环境类
// 环境类
public class Screen {
// 当前状态,正常状态,二倍状态,四倍状态
private ScreenState currentState,nomalState,twoState,fourState;
public Screen() {
nomalState = new NormalState();
twoState = new TwoState();
fourState = new FourState();
currentState = nomalState;
currentState.display();
}
public void setState(ScreenState state) {
currentState = state;
}
// 模拟单击事件 - 封装了对状态类中业务方法的调用和状态转换
public void click() {
if(currentState == nomalState) {
setState(twoState);
currentState.display();
}else if(currentState == twoState) {
setState(fourState);
currentState.display();
}else if(currentState == fourState) {
setState(nomalState);
currentState.display();
}
}
}
4.测试
public class Client {
public static void main(String[] args) {
Screen sc = new Screen();
sc.click();
sc.click();
sc.click();
}
}
5.运行截图
如果增加新的状态类,需要修改环境类里的代码,一定程度上违背了开闭原则。
优点
1.封装了状态的转换规则,可以将状态在环境类或具体状态类中集中管理。
2.将所有与某个状态相关的行为放在一个类里,只需注入不同的状态,就可以拥有不同的行为。
3.避免了大量的条件语句将业务代码和状态转换代码交织在一起。
4.可以让多个环境对象共享一个状态对象,减少系统中对象的个数。
缺点
1.结构和实现较复杂
2.一定程度违背开闭原则