状态模式
允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。其别名为状态对象,同样是一种对象行为型模式。
存在问题
(1)几乎每个方法都包含判断语句,以判断在该状态下是否具有该方法以及在特定状态下该方法如何实现,导致代码非常冗长,可维护性非常差。
(2)拥有一个较为复杂的setCheck方法,包含大量的if…else用于进行状态转换,代码测试难度非常大,且不易于维护。
(3)扩展性差,如果新增一个冻结状态,则修要对原有代码进行大量修改,扩展起来非常麻烦。
状态模式就是为了解决上述问题,状态模式中将对象在每一个状态下的行为和状态转移语句封装在一个状态类中,通过这些状态来分散冗长的条件转移语句,让系统有更好的灵活性和可扩展性。
结构图
(1)Context(环境类)可以称为上下文类,拥有多种状态,由于环境类的状态存在多样性且在不同状态下的行为有所不同,因此将状态独立出去形成单独的状态类,以一个抽象状态类的引用进行维护,这个属性定义当前的状态,具体实现时,它是一个具体状态对象。
(2)State(抽象状态类)是一个接口,定义一个用于封装与环境类的一个特定状态相关的行为,在抽象状态类中声明各种不同状态对应的方法,而在其子类实现这些方法,由于不同状态下对象的行为不同,因此在不同子类中的实现可能不同,相同的方法可以写在抽象类中。
(3)ConcreteState(具体状态类)它是抽象状态类的子类,每一个子类实现一个与环境类的一个状态相关的行为,每一个具体状态类对应一个状态,不同的具体状态类其行为有所不同。
package com.learn.designmode.mode.state;
import lombok.Data;
/**
* 环境类
*/
@Data
public class Context {
// 维持对一个抽象对象的引用
private State state;
// 其他属性值,该属性值的变化可能会导致对象状态发生变化
private int value;
public void request(){
state.handle();
}
}
package com.learn.designmode.mode.state;
/**
* 抽象状态类
*/
public abstract class State {
// 声明抽象方法,不同具体状态类可以有不同的实现
public abstract void handle();
}
class ConcreteState extends State{
@Override
public void handle() {
// 方法具体实现代码
}
}
状态模式使用过程中,一个对象的状态之间还可以进行相互转换,通常有以下两种方式
- 统一由环境类来负责状态之间的转换,此时环境类还充当了状态管理器(State Manager)角色,在环境类的业务方法中通过对某些属性值判断实现状态转换,可以提供一个专门的方法用于实现属性判断和状态转换。
public void changeState(){
if (this.value == 1){
state = new ConcreteState();
}else {
// ...对应其他具体状态类
}
}
- 有具体状态类负责状态之间的转换,在具体状态类的业务方法中判断环境类的某些属性值再根据情况为环境类设置新的状态对象,实现状态转换。同样也可以提供一个专门的方法来负责属性值的判断和状态转换。此时,状态类和环境类为依赖关系。
public void changeState(Context context){
if (context.getValue() == 1){
context.setState(new ConcreteState());
}else {
// set其他具体状态类
}
}
统一由环境类来负责状态之间的转换:比较统一,也不用再具体状态里写代码了,新增具体状态肯定要改修改状态的代码。由具体状态类来负责状态之间的转换:每个具体类都要实现,状态改变的代码,代码写的比上面这种多,好处是,新增一个状态可能只要改几个具体状态类就行。
完整的解决方案
package com.learn.designmode.mode.state.demo;
import lombok.Data;
import java.math.BigDecimal;
/**
* 环境类
*/
@Data
public class AccountContext {
private BaseState baseState;
private String owner;
private BigDecimal balance;
public AccountContext(String owner,BigDecimal balance){
this.balance = balance;
this.owner = owner;
this.baseState = new NormalState(this);
System.out.println(this.owner + "开户,金额为 " +this.balance);
System.out.println("---------------------------------");
}
// 存款
public void deposit(BigDecimal amount){
System.out.println(this.owner + "存款 " + amount);
// 调用状态对象的deposit方法
baseState.deposit(amount);
System.out.println("现在余额为" + this.balance);
System.out.println("现在账户状态为 " + this.baseState.getClass());
System.out.println("------------------------------------");
}
// 取款
public void withdraw(BigDecimal amount){
System.out.println(this.owner + "取款 " + amount);
baseState.withdraw(amount);
System.out.println("现在余额为" + this.balance);
System.out.println("现在账户状态为 " + this.baseState.getClass());
System.out.println("------------------------------------");
}
// 计算利息
public void computeInterest(){
baseState.computeInterest();
}
}
package com.learn.designmode.mode.state.demo;
import java.math.BigDecimal;
/**
*
* 抽象状态类
*/
public abstract class BaseState {
public AccountContext accountContext;
abstract void deposit(BigDecimal bigDecimal);
abstract void withdraw(BigDecimal bigDecimal);
abstract void computeInterest();
abstract void stateCheck();
}
/**
* 正常状态
*/
class NormalState extends BaseState{
public NormalState(AccountContext context){
this.accountContext = context;
}
public NormalState(BaseState baseState){
this.accountContext = baseState.accountContext;
}
@Override
void deposit(BigDecimal money) {
this.accountContext.setBalance(this.accountContext.getBalance().add(money));
this.stateCheck();
}
@Override
void withdraw(BigDecimal money) {
this.accountContext.setBalance(this.accountContext.getBalance().subtract(money));
this.stateCheck();
}
@Override
void computeInterest() {
System.out.println("正常状态,无须利息!");
}
@Override
// 状态转换
void stateCheck() {
BigDecimal money = this.accountContext.getBalance();
if (money.compareTo(new BigDecimal(-2000)) > 0 && money.compareTo(new BigDecimal(0)) <= 0){
this.accountContext.setBaseState(new OverdraftState(this));
}else if (money.compareTo(new BigDecimal(-2000)) == 0){
this.accountContext.setBaseState(new RestrictedState(this));
}else if (money.compareTo(new BigDecimal(-2000)) <0){
System.out.println("操作受阻");
}
}
}
/**
* 受阻状态
*/
class RestrictedState extends BaseState{
public RestrictedState(AccountContext context){
this.accountContext = context;
}
public RestrictedState(BaseState baseState){
this.accountContext = baseState.accountContext;
}
@Override
void deposit(BigDecimal money) {
this.accountContext.setBalance(this.accountContext.getBalance().add(money));
this.stateCheck();
}
@Override
void withdraw(BigDecimal money) {
System.out.println("账号受限,无法取款 ");
}
@Override
void computeInterest() {
System.out.println("计算利息");
}
@Override
// 状态转换
void stateCheck() {
BigDecimal money = this.accountContext.getBalance();
if (money.compareTo(new BigDecimal(0)) > 0){
this.accountContext.setBaseState(new NormalState(this));
}else if (money.compareTo(new BigDecimal(-2000)) > 0){
this.accountContext.setBaseState(new OverdraftState(this));
}
}
}
/**
* 透支状态
*/
class OverdraftState extends BaseState{
public OverdraftState(AccountContext context){
this.accountContext = context;
}
public OverdraftState(BaseState baseState){
this.accountContext = baseState.accountContext;
}
@Override
void deposit(BigDecimal money) {
this.accountContext.setBalance(this.accountContext.getBalance().add(money));
this.stateCheck();
}
@Override
void withdraw(BigDecimal money) {
this.accountContext.setBalance(this.accountContext.getBalance().subtract(money));
this.stateCheck();
}
@Override
void computeInterest() {
System.out.println("计算利息");
}
@Override
// 状态转换
void stateCheck() {
BigDecimal money = this.accountContext.getBalance();
if (money.compareTo(new BigDecimal(0)) > 0){
this.accountContext.setBaseState(new NormalState(this));
}else if (money.compareTo(new BigDecimal(-2000)) == 0){
this.accountContext.setBaseState(new RestrictedState(this));
}else if (money.compareTo(new BigDecimal(0)) < 0){
System.out.println("操作受限");
}
}
}
package com.learn.designmode.mode.state.demo;
import java.math.BigDecimal;
public class Client {
public static void main(String[] args) {
AccountContext accountContext = new AccountContext("zlx",new BigDecimal(0.0));
accountContext.deposit(new BigDecimal(1000));
accountContext.withdraw(new BigDecimal(2000));
accountContext.deposit(new BigDecimal(3000));
accountContext.withdraw(new BigDecimal(4000));
accountContext.withdraw(new BigDecimal(1000));
accountContext.computeInterest();
}
}
共享状态
某些情况下多个环境对象可能需要共享同一个状态,如果希望系统中实现多个环境对象共享一个或多个状态对象,需要将这些状态对象定义为环境类的静态成员变量对象。
如果系统要求两个开关对象要么处于开的状态,要么都处于关的状态,状态必须保持一致,并且可以相互转换。
package com.learn.designmode.mode.state.shareDemo;
import lombok.Data;
/**
* 开关类
*/
@Data
public class Switch {
private static State state,onState,offState;
private String name;
public void setState(State state1){
state = state1;
}
public Switch(String name){
this.name = name;
onState = new OnState();
offState = new OffState();
state = onState;
}
public static State getState(String type){
if (type.equals("on")){
return onState;
}else {
return offState;
}
}
public void on(){
System.out.print(name);
state.on(this);
}
public void off(){
System.out.print(name);
state.off(this);
}
}
package com.learn.designmode.mode.state.shareDemo;
/**
* 抽象状态类
*/
public abstract class State {
public abstract void on(Switch s);
public abstract void off(Switch s);
}
/**
* 开状态
*/
class OnState extends State{
@Override
public void on(Switch s) {
System.out.println("已经打开");
}
@Override
public void off(Switch s) {
System.out.println("关闭");
s.setState(Switch.getState("off"));
}
}
/**
* 开状态
*/
class OffState extends State{
@Override
public void on(Switch s) {
System.out.println("打开");
s.setState(Switch.getState("on"));
}
@Override
public void off(Switch s) {
System.out.println("已经关闭");
s.setState(Switch.getState("off"));
}
}
package com.learn.designmode.mode.state.shareDemo;
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();
s2.on();
s1.on();
}
}
输出结果
从输出结果可以看出,开关1和开关2共享状态。
使用环境类实现状态转换
package com.learn.designmode.mode.state.typedemo;
import javafx.scene.Scene;
import lombok.Data;
import java.util.Stack;
@Data
public class Screen {
// 存储所有状态
private State normalState,largestState,largerState,currentState;
public Screen(){
normalState = new NormalState();
largerState = new LargerState();
largestState = new LargestState();
// 设置初始状态
currentState = normalState;
currentState.display();
}
/**
* 点击事件处理办法,封装了对状态类中的业务方法的调用状态和转换
*/
public void onClick(){
if (this.currentState == normalState){
this.setCurrentState(largerState);
this.getCurrentState().display();
}else if (this.currentState == largerState){
this.setCurrentState(largestState);
this.getCurrentState().display();
}else {
this.setCurrentState(normalState);
this.getCurrentState().display();
}
}
}
/**
* 抽象状态类
*/
abstract class State{
public abstract void display();
}
/**
* 正常大小
*/
class NormalState extends State{
@Override
public void display() {
System.out.println("正常大小");
}
}
/**
* 2倍大小
*/
class LargerState extends State{
@Override
public void display() {
System.out.println("2倍大小");
}
}
/**
* 2倍大小
*/
class LargestState extends State{
@Override
public void display() {
System.out.println("4倍大小");
}
}
package com.learn.designmode.mode.state.typedemo;
public class Client {
public static void main(String[] args) {
Screen screen = new Screen();
screen.onClick();
screen.onClick();
screen.onClick();
}
}
总结
优点
(1)封装了状态的转换规则,状态模式中可以将状态转换代码封装在环境类或者具体状态类中,可以集中对状态转换代码进行管理,而不是分散在一个个业务方法中
(2)将所有与某个状态有关的行为放到一个类中,只需要注入一个不同的状态即可使环境对象拥有不同的行为。
(3)允许状态更换逻辑与状态对象合成一体,而不是提供一个巨大的条件语句块,状态模式可以避免使用庞大的条件语句来将业务方法和状态转换代码交织在一起。
(4)可以让多个环境对象共享一个状态,减少系统的类的个数
缺点
(1)状态模式的使用必然会增加系统中类的个数,导致系统开销过大。
(2)状态模式的程序结构与实现都较为复杂,如果使用不当,会造成代码混乱,增加系统难度。
(3)状态模式对开闭原则的支持并不太好,增加新的状态类需要修改那些负责状态转换的源代码,否则无法转换到新增状态,修改某个状态类的行为也需要修改源代码
适用场景
(1)对象的行为依赖于它的状态(例如某些属性值),状态的改变将导致行为的变化
(2)在代码中包含大量与对象状态有关的条件语句,这些条件语句的出现会导致代码的可维护性和灵活性变差,不能方便地增加和删除状态,并且导致客户类与类库之间的耦合度增强