如何避免状态设计模式中Context类(如Car类)职责过载?
首先明确:这绝对是个需要重视的问题。当你不断给Context(Car类)添加状态操作转发逻辑,再叠加速度、油量这类核心属性管理时,Car类会逐渐变成上帝类——职责混杂,既管状态流转、又管属性维护、还要做所有操作的转发入口,最终导致代码耦合度高、维护成本飙升:比如改个油量计算逻辑都可能不小心影响状态转发,新增状态操作还要在Car里加一堆空实现或转发方法,完全违背了状态模式原本“分离逻辑、提升扩展性”的初衷。
下面是几个针对性的解决思路:
1. 拆分核心属性到独立类
把速度、油量、里程这类车辆固有状态属性,抽成一个单独的CarMetrics类,让它负责属性的计算、更新和校验(比如油量不能为负、速度上限判断)。Car类只需要持有CarMetrics的实例,状态类需要访问这些属性时,通过Car拿到CarMetrics即可。
举个简单的代码片段:
// 抽离的属性管理类 class CarMetrics { private int fuelLevel; private int currentSpeed; public void consumeFuel(int amount) { if (fuelLevel >= amount) fuelLevel -= amount; // 处理油量不足逻辑 } public void increaseSpeed(int delta) { currentSpeed = Math.min(currentSpeed + delta, 120); // 限速120 } // getter/setter... } // Context类只负责协调 class Car { private CarState currentState; private CarMetrics metrics; public Car() { metrics = new CarMetrics(); currentState = new ParkState(); } public CarMetrics getMetrics() { return metrics; } // 状态切换逻辑 public void setState(CarState newState) { currentState.exit(this); currentState = newState; currentState.enter(this); } } // 状态类访问属性通过Car.getMetrics() class DriveState implements CarState { @Override public void accelerate(Car car) { car.getMetrics().increaseSpeed(10); // 其他驾驶逻辑 } }
2. 按功能拆分状态操作接口
不要给所有状态定义一个包含所有操作的大CarState接口,而是拆分成多个细粒度的功能接口。比如:
EntertainmentOperations:包含playMovie()、switchRadio()等娱乐操作DrivingOperations:包含accelerate()、brake()等驾驶操作ParkingOperations:包含engageHandbrake()、enableParkingAC()等停车操作
然后让不同状态实现对应的接口:比如NeutralState实现EntertainmentOperations,DriveState实现DrivingOperations,ParkState同时实现EntertainmentOperations和ParkingOperations。
这样Context(Car)不需要为每个操作都写转发方法,而是根据当前状态的能力来调用:
class Car { private CarState currentState; private CarMetrics metrics; // 娱乐操作仅当状态支持时才调用 public void playMovie() { if (currentState instanceof EntertainmentOperations) { ((EntertainmentOperations) currentState).playMovie(this); } else { // 处理不支持的情况,比如Drive状态下提示不能播放影片 } } // 驾驶操作同理 public void accelerate() { if (currentState instanceof DrivingOperations) { ((DrivingOperations) currentState).accelerate(this); } } }
3. 引入状态管理器分离状态流转逻辑
如果状态切换的规则越来越复杂(比如某些状态之间不能直接切换),可以把状态的创建、切换规则抽成CarStateManager类,让它负责管理所有状态实例、处理状态切换的合法性校验。Car类只需要和状态管理器交互,不用关心状态的具体细节:
class CarStateManager { private static final ParkState PARK_STATE = new ParkState(); private static final DriveState DRIVE_STATE = new DriveState(); private static final NeutralState NEUTRAL_STATE = new NeutralState(); public boolean canSwitch(CarState current, CarState target, Car car) { // 比如Drive状态不能直接切到Park,必须先切到Neutral if (current instanceof DriveState && target instanceof ParkState) { return false; } // 其他校验逻辑,比如油量不足不能切到Drive return car.getMetrics().getFuelLevel() > 0; } public CarState getParkState() { return PARK_STATE; } // 其他状态获取方法... } class Car { private CarState currentState; private CarMetrics metrics; private CarStateManager stateManager; public Car() { metrics = new CarMetrics(); stateManager = new CarStateManager(); currentState = stateManager.getParkState(); } public void switchToPark() { CarState target = stateManager.getParkState(); if (stateManager.canSwitch(currentState, target, this)) { setState(target); } } }
4. 用委托模式拆分操作入口
如果某些操作(比如娱乐系统)本身就有复杂的逻辑,可以把这些操作的实现完全委托给独立的组件,比如CarEntertainmentSystem,状态类只需要触发对应的组件逻辑,而Context不用做转发:
class CarEntertainmentSystem { public void playMovie() { // 播放影片的具体逻辑:加载资源、显示画面等 } public void stopMovie() { // 停止播放逻辑 } } class Car { private CarState currentState; private CarMetrics metrics; private CarEntertainmentSystem entertainmentSystem; // 提供给状态类的访问方法 public CarEntertainmentSystem getEntertainmentSystem() { return entertainmentSystem; } } class NeutralState implements CarState, EntertainmentOperations { @Override public void playMovie(Car car) { car.getEntertainmentSystem().playMovie(); } }
这些方案的核心都是单一职责原则——让每个类只负责一件事:Car类作为Context只负责协调各组件,状态类只负责对应状态下的逻辑,属性类只负责数据管理,功能组件只负责特定业务逻辑。这样即使后续新增更多状态操作或属性,也不会让Context类膨胀失控。
内容的提问来源于stack exchange,提问作者Angel Hadzhiev

