状态模式
概述
很多情况下,一个对象的行为取决于一个或多个动态变化的属性,这样的属性叫做状态。一个对象可以拥有多个状态,这些状态可以相互转换,当对象状态不同时,其行为也有所差异
假设一个人就是对象,人根据心情不同会有很多状态,比如开心和伤心,这两种状态可以相互转换。开心的人可能会因为突然的噩耗而伤心,过了一段时间后,又可能因为其他好事而开心。不同状态下人的行为也不同,有些人伤心时会通过运动、旅行、听音乐来缓解心情,而开心时则可能会唱歌、跳舞、请客吃饭等等
再来考虑软件系统中的情况,如某酒店订房系统,可以将房间设计为一个类,房间对象有已预订、空闲、已入住等情况,这些状态之间可以相互转换,并且不同状态的对象可能具有不同的行为,如已预订或已入住的房间不能再接收其他顾客的预订,而空闲的房间可以接受预订
我们可以使用复杂的条件判断来进行状态判断和转换操作,这会导致代码的可维护性和灵活性下降,当出现新的状态时必须修改代码。在状态模式中,可以将对象状态从包含该状态的类中分离出来,做成一个单独的状态类,将状态和状态对应的行为分离
模式定义
允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。其别名为状态对象(Objects for States),状态模式是一种对象行为型模式。
Allow an object to alter its behavior when its internal state changes. The object will appear to change its class.
模式分析
我们把拥有状态的对象称为环境类,也叫上下文类。再引入一个抽象状态类来专门表示对象的状态,对象的每一种具体状态类都继承该抽象类,不同具体状态类实现不同状态的行为,包括各种状态之间的转换。在环境类中维护一个抽象状态类 State 的实例,用来定义当前状态
得到状态模式结构类图如下:
环境类中的 request() 方法处理业务逻辑,根据状态去调用对应的 handle() 方法,如果需要切换状态,还提供了 setState() 用于设置当前房间状态。如果我们希望执行操作后状态自动发生改变,那么我们还需要在 State 中定义一个 Context 对象,实现一个双向依赖关系
考虑前面提到的订房系统,如果不使用状态模式,可能就会存在如下代码:
if (state == "空闲") {
if (预订房间) {
预订操作;
state = "已预订";
} else if (住进房间) {
入住操作;
state = "已入住";
}
} else if(state == "已预订") {
if (住进房间) {
入住操作;
state = "已入住";
} else if (取消预订) {
取消操作;
state = "空闲";
}
}
上述代码需要做频繁且复杂的判断操作,可维护性很差。因此考虑使用状态模式将房间类的状态分离出来,将与每种状态有关的操作封装在独立的状态类中
来写一个完整的示例
环境类(Room)
public class Room {
// 维护一个状态对象
private State state;
public Room() {
// 默认为空闲状态
this.state = new IdleState(this);
}
public State getState() {
return state;
}
public void setState(State state) {
this.state = state;
}
public void reserve() {
state.reserve();
}
public void checkIn() {
state.checkIn();
}
public void cancelReserve() {
state.cancelReserve();
}
public void checkOut() {
state.checkOut();
}
}
抽象状态类(State)
public abstract class State {
// 用于状态转换
protected Room room;
public State(Room room) {
this.room = room;
}
public abstract void reserve();
public abstract void checkIn();
public abstract void cancelReserve();
public abstract void checkOut();
}
具体状态类(IdleState)
public class IdleState extends State {
public IdleState(Room room) {
super(room);
}
@Override
public void reserve() {
System.out.println("房间预订成功");
// 切换状态
room.setState(new ReservedState(room));
}
@Override
public void checkIn() {
System.out.println("房间入住成功");
room.setState(new InhabitedState(room));
}
@Override
public void cancelReserve() {
System.out.println("无法取消预订,房间处于空闲状态");
}
@Override
public void checkOut() {
System.out.println("无法退房,房间处于空闲状态");
}
}
具体状态类(ReservedState)
public class ReservedState extends State {
public ReservedState(Room room) {
super(room);
}
@Override
public void reserve() {
System.out.println("无法预订,房间处于已预订状态");
}
@Override
public void checkIn() {
System.out.println("房间入住成功");
room.setState(new InhabitedState(room));
}
@Override
public void cancelReserve() {
System.out.println("取消预订成功");
room.setState(new IdleState(room));
}
@Override
public void checkOut() {
System.out.println("无法退房,房间处于已预订状态");
}
}
具体状态类(InhabitedState)
public class InhabitedState extends State {
public InhabitedState(Room room) {
super(room);
}
@Override
public void reserve() {
System.out.println("无法预订,房间处于入住状态");
}
@Override
public void checkIn() {
System.out.println("无法入住,房间处于入住状态");
}
@Override
public void cancelReserve() {
System.out.println("无法取消预订,房间处于入住状态");
}
@Override
public void checkOut() {
System.out.println("退房成功");
room.setState(new IdleState(room));
}
}
测试类(Client)
public class Client {
public static void main(String[] args) {
Room room = new Room();
room.cancelReserve();
room.checkOut();
room.reserve();
System.out.println("--------------------------");
room.reserve();
room.checkOut();
room.checkIn();
System.out.println("--------------------------");
room.reserve();
room.checkIn();
room.cancelReserve();
room.checkOut();
}
}
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战