状态模式(State Pattern)
概念
状态模式是一种行为设计模式,它允许对象在内部状态改变时改变它的行为,对象看起来似乎修改了它的类。状态模式将对象的行为封装在不同的状态对象中,通过切换状态对象来改变对象的行为。
状态模式的核心思想是将对象的状态抽象为独立的类,每个状态类都实现相同的接口,对象通过委托给当前状态对象来处理请求。当状态改变时,对象会切换到另一个状态对象,从而改变其行为。
基本实现
状态模式包含以下主要角色:
- State(抽象状态类):定义一个接口以封装与Context的一个特定状态相关的行为。
- ConcreteState(具体状态类):每一个子类实现一个与Context的一个状态相关的行为。
- Context(环境类):维护一个ConcreteState子类的实例,这个实例定义当前的状态。
下面是状态模式的基本实现:
javascript
// 抽象状态类
class State {
handle(context) {
throw new Error('handle method must be implemented');
}
}
// 具体状态A
class ConcreteStateA extends State {
handle(context) {
console.log('当前状态是 A,执行A状态的行为');
// 切换到状态B
context.setState(new ConcreteStateB());
}
}
// 具体状态B
class ConcreteStateB extends State {
handle(context) {
console.log('当前状态是 B,执行B状态的行为');
// 切换到状态A
context.setState(new ConcreteStateA());
}
}
// 环境类
class Context {
constructor() {
this.state = new ConcreteStateA(); // 初始状态
}
setState(state) {
this.state = state;
}
request() {
this.state.handle(this);
}
}
// 使用示例
const context = new Context();
context.request(); // 状态A
context.request(); // 状态B
context.request(); // 状态A
context.request(); // 状态B实际应用场景
场景1:订单状态管理系统
在电商系统中,订单有不同的状态(待付款、已付款、已发货、已完成、已取消等),每个状态有不同的行为和可执行的操作。
javascript
// 订单状态接口
class OrderState {
pay(order) {
throw new Error('pay method not implemented');
}
ship(order) {
throw new Error('ship method not implemented');
}
deliver(order) {
throw new Error('deliver method not implemented');
}
cancel(order) {
throw new Error('cancel method not implemented');
}
getStateName() {
throw new Error('getStateName method not implemented');
}
}
// 待付款状态
class PendingPaymentState extends OrderState {
pay(order) {
console.log('订单已付款');
order.setState(new PaidState());
}
ship(order) {
console.log('订单还未付款,无法发货');
}
deliver(order) {
console.log('订单还未付款,无法配送');
}
cancel(order) {
console.log('订单已取消');
order.setState(new CancelledState());
}
getStateName() {
return '待付款';
}
}
// 已付款状态
class PaidState extends OrderState {
pay(order) {
console.log('订单已付款,无需重复付款');
}
ship(order) {
console.log('订单已发货');
order.setState(new ShippedState());
}
deliver(order) {
console.log('订单还未发货,无法配送');
}
cancel(order) {
console.log('订单已取消,将退款');
order.setState(new CancelledState());
}
getStateName() {
return '已付款';
}
}
// 已发货状态
class ShippedState extends OrderState {
pay(order) {
console.log('订单已发货,无法付款');
}
ship(order) {
console.log('订单已发货,无需重复发货');
}
deliver(order) {
console.log('订单已配送');
order.setState(new DeliveredState());
}
cancel(order) {
console.log('订单已在配送途中,无法取消');
}
getStateName() {
return '已发货';
}
}
// 已配送状态
class DeliveredState extends OrderState {
pay(order) {
console.log('订单已完成,无法付款');
}
ship(order) {
console.log('订单已完成,无法发货');
}
deliver(order) {
console.log('订单已完成,无需重复配送');
}
cancel(order) {
console.log('订单已完成,无法取消');
}
getStateName() {
return '已配送';
}
}
// 已取消状态
class CancelledState extends OrderState {
pay(order) {
console.log('订单已取消,无法付款');
}
ship(order) {
console.log('订单已取消,无法发货');
}
deliver(order) {
console.log('订单已取消,无法配送');
}
cancel(order) {
console.log('订单已取消,无需重复取消');
}
getStateName() {
return '已取消';
}
}
// 订单类(环境类)
class Order {
constructor(orderId) {
this.orderId = orderId;
this.state = new PendingPaymentState(); // 初始状态为待付款
}
setState(state) {
this.state = state;
}
getStateName() {
return this.state.getStateName();
}
pay() {
console.log(`订单 ${this.orderId} 尝试付款...`);
this.state.pay(this);
}
ship() {
console.log(`订单 ${this.orderId} 尝试发货...`);
this.state.ship(this);
}
deliver() {
console.log(`订单 ${this.orderId} 尝试配送...`);
this.state.deliver(this);
}
cancel() {
console.log(`订单 ${this.orderId} 尝试取消...`);
this.state.cancel(this);
}
displayStatus() {
console.log(`订单 ${this.orderId} 当前状态: ${this.getStateName()}\n`);
}
}
// 使用示例
const order = new Order('ORD-001');
order.displayStatus();
// 尝试各种操作
order.ship(); // 无法发货
order.deliver(); // 无法配送
order.pay(); // 付款成功
order.displayStatus();
order.ship(); // 发货成功
order.displayStatus();
order.deliver(); // 配送成功
order.displayStatus();
// 尝试在已完成状态下操作
order.pay(); // 无法付款
order.ship(); // 无法发货
order.cancel(); // 无法取消场景2:媒体播放器状态控制
在媒体播放器中,播放状态(播放、暂停、停止)决定了可以执行的操作,使用状态模式可以很好地管理这些状态转换。
javascript
// 播放器状态接口
class PlayerState {
play(player) {
throw new Error('play method not implemented');
}
pause(player) {
throw new Error('pause method not implemented');
}
stop(player) {
throw new Error('stop method not implemented');
}
getStateName() {
throw new Error('getStateName method not implemented');
}
}
// 停止状态
class StoppedState extends PlayerState {
play(player) {
console.log('开始播放');
player.setState(new PlayingState());
}
pause(player) {
console.log('播放器已停止,无法暂停');
}
stop(player) {
console.log('播放器已停止');
}
getStateName() {
return '停止';
}
}
// 播放状态
class PlayingState extends PlayerState {
play(player) {
console.log('正在播放中');
}
pause(player) {
console.log('暂停播放');
player.setState(new PausedState());
}
stop(player) {
console.log('停止播放');
player.setState(new StoppedState());
}
getStateName() {
return '播放';
}
}
// 暂停状态
class PausedState extends PlayerState {
play(player) {
console.log('继续播放');
player.setState(new PlayingState());
}
pause(player) {
console.log('已暂停');
}
stop(player) {
console.log('停止播放');
player.setState(new StoppedState());
}
getStateName() {
return '暂停';
}
}
// 媒体播放器类
class MediaPlayer {
constructor() {
this.state = new StoppedState(); // 初始状态为停止
this.currentMedia = null;
}
setState(state) {
this.state = state;
}
getStateName() {
return this.state.getStateName();
}
loadMedia(media) {
this.currentMedia = media;
console.log(`加载媒体: ${media}`);
}
play() {
console.log('播放器尝试播放...');
this.state.play(this);
}
pause() {
console.log('播放器尝试暂停...');
this.state.pause(this);
}
stop() {
console.log('播放器尝试停止...');
this.state.stop(this);
}
displayStatus() {
console.log(`播放器状态: ${this.getStateName()}`);
if (this.currentMedia) {
console.log(`当前媒体: ${this.currentMedia}`);
}
console.log('');
}
}
// 使用示例
const player = new MediaPlayer();
player.loadMedia('song.mp3');
player.displayStatus();
// 尝试各种操作
player.pause(); // 无法暂停
player.stop(); // 已停止
player.play(); // 开始播放
player.displayStatus();
player.pause(); // 暂停播放
player.displayStatus();
player.play(); // 继续播放
player.displayStatus();
player.stop(); // 停止播放
player.displayStatus();
// 在停止状态下尝试操作
player.pause(); // 无法暂停场景3:游戏角色状态系统
在游戏开发中,角色可能有多种状态(正常、中毒、眩晕、隐身等),每种状态会影响角色的行为和能力。
javascript
// 角色状态接口
class CharacterState {
move(character) {
throw new Error('move method not implemented');
}
attack(character, target) {
throw new Error('attack method not implemented');
}
takeDamage(character, damage) {
throw new Error('takeDamage method not implemented');
}
useSkill(character, skill) {
throw new Error('useSkill method not implemented');
}
getStateName() {
throw new Error('getStateName method not implemented');
}
// 状态更新(每帧调用)
update(character) {
// 默认实现,子类可重写
}
}
// 正常状态
class NormalState extends CharacterState {
move(character) {
console.log(`${character.name} 正常移动`);
}
attack(character, target) {
const damage = character.attackPower;
console.log(`${character.name} 攻击 ${target.name} 造成 ${damage} 点伤害`);
target.takeDamage(damage);
}
takeDamage(character, damage) {
character.health -= damage;
console.log(`${character.name} 受到 ${damage} 点伤害,剩余生命值: ${character.health}`);
if (character.health <= 0) {
console.log(`${character.name} 被击败!`);
}
}
useSkill(character, skill) {
console.log(`${character.name} 使用技能: ${skill}`);
return true;
}
getStateName() {
return '正常';
}
}
// 中毒状态
class PoisonedState extends CharacterState {
constructor() {
super();
this.duration = 5; // 持续5个回合
this.poisonDamage = 10;
}
move(character) {
console.log(`${character.name} 中毒状态下缓慢移动`);
}
attack(character, target) {
const damage = Math.floor(character.attackPower * 0.8); // 攻击力降低
console.log(`${character.name} 中毒状态下攻击 ${target.name} 造成 ${damage} 点伤害`);
target.takeDamage(damage);
}
takeDamage(character, damage) {
const actualDamage = damage * 1.2; // 受到更多伤害
character.health -= actualDamage;
console.log(`${character.name} 中毒状态下受到 ${actualDamage} 点伤害(+20%),剩余生命值: ${character.health}`);
if (character.health <= 0) {
console.log(`${character.name} 被击败!`);
}
}
useSkill(character, skill) {
console.log(`${character.name} 中毒状态下无法使用技能 ${skill}`);
return false;
}
update(character) {
this.duration--;
character.health -= this.poisonDamage;
console.log(`${character.name} 受到 ${this.poisonDamage} 点毒性伤害,剩余生命值: ${character.health},中毒状态剩余 ${this.duration} 回合`);
if (this.duration <= 0) {
console.log(`${character.name} 中毒状态结束`);
character.setState(new NormalState());
}
}
getStateName() {
return `中毒 (${this.duration}回合)`;
}
}
// 眩晕状态
class StunnedState extends CharacterState {
constructor() {
super();
this.duration = 2; // 持续2个回合
}
move(character) {
console.log(`${character.name} 眩晕状态下无法移动`);
}
attack(character, target) {
console.log(`${character.name} 眩晕状态下无法攻击`);
}
takeDamage(character, damage) {
character.health -= damage;
console.log(`${character.name} 眩晕状态下受到 ${damage} 点伤害,剩余生命值: ${character.health}`);
if (character.health <= 0) {
console.log(`${character.name} 被击败!`);
}
}
useSkill(character, skill) {
console.log(`${character.name} 眩晕状态下无法使用技能 ${skill}`);
return false;
}
update(character) {
this.duration--;
console.log(`${character.name} 眩晕状态剩余 ${this.duration} 回合`);
if (this.duration <= 0) {
console.log(`${character.name} 眩晕状态结束`);
character.setState(new NormalState());
}
}
getStateName() {
return `眩晕 (${this.duration}回合)`;
}
}
// 隐身状态
class InvisibleState extends CharacterState {
constructor() {
super();
this.duration = 3; // 持续3个回合
}
move(character) {
console.log(`${character.name} 隐身状态下快速移动`);
}
attack(character, target) {
const damage = Math.floor(character.attackPower * 1.5); // 隐身攻击有加成
console.log(`${character.name} 从隐身状态发动突袭,对 ${target.name} 造成 ${damage} 点暴击伤害`);
target.takeDamage(damage);
// 攻击后解除隐身
character.setState(new NormalState());
}
takeDamage(character, damage) {
const actualDamage = Math.floor(damage * 0.5); // 受到伤害减半
character.health -= actualDamage;
console.log(`${character.name} 隐身状态下受到 ${actualDamage} 点伤害(-50%),剩余生命值: ${character.health}`);
if (character.health <= 0) {
console.log(`${character.name} 被击败!`);
}
}
useSkill(character, skill) {
console.log(`${character.name} 隐身状态下使用技能: ${skill}`);
// 使用技能后有一定概率解除隐身
if (Math.random() < 0.3) {
console.log(`${character.name} 使用技能后暴露了位置`);
character.setState(new NormalState());
}
return true;
}
update(character) {
this.duration--;
console.log(`${character.name} 隐身状态剩余 ${this.duration} 回合`);
if (this.duration <= 0) {
console.log(`${character.name} 隐身状态结束`);
character.setState(new NormalState());
}
}
getStateName() {
return `隐身 (${this.duration}回合)`;
}
}
// 游戏角色类
class GameCharacter {
constructor(name, health, attackPower) {
this.name = name;
this.health = health;
this.maxHealth = health;
this.attackPower = attackPower;
this.state = new NormalState();
}
setState(state) {
this.state = state;
}
getStateName() {
return this.state.getStateName();
}
move() {
console.log(`${this.name} 尝试移动...`);
this.state.move(this);
}
attack(target) {
console.log(`${this.name} 尝试攻击...`);
this.state.attack(this, target);
}
takeDamage(damage) {
this.state.takeDamage(this, damage);
}
useSkill(skill) {
console.log(`${this.name} 尝试使用技能...`);
return this.state.useSkill(this, skill);
}
// 被施加中毒状态
applyPoison() {
if (!(this.state instanceof PoisonedState)) {
console.log(`${this.name} 被施加了中毒状态!`);
this.setState(new PoisonedState());
} else {
console.log(`${this.name} 已经中毒了`);
}
}
// 被施加眩晕状态
applyStun() {
if (!(this.state instanceof StunnedState)) {
console.log(`${this.name} 被眩晕了!`);
this.setState(new StunnedState());
} else {
console.log(`${this.name} 已经被眩晕了`);
}
}
// 进入隐身状态
turnInvisible() {
if (!(this.state instanceof InvisibleState)) {
console.log(`${this.name} 进入了隐身状态!`);
this.setState(new InvisibleState());
} else {
console.log(`${this.name} 已经隐身了`);
}
}
// 状态更新(每回合调用)
update() {
this.state.update(this);
}
displayStatus() {
console.log(`\n=== ${this.name} 状态 ===`);
console.log(`生命值: ${this.health}/${this.maxHealth}`);
console.log(`攻击力: ${this.attackPower}`);
console.log(`当前状态: ${this.getStateName()}\n`);
}
}
// 使用示例
const player = new GameCharacter('勇士', 100, 20);
const enemy = new GameCharacter('怪物', 80, 15);
player.displayStatus();
enemy.displayStatus();
console.log('=== 战斗开始 ===');
// 正常状态下的行动
player.move();
player.attack(enemy);
enemy.displayStatus();
// 敌人中毒
enemy.applyPoison();
enemy.displayStatus();
// 中毒状态下的行动
enemy.move();
enemy.attack(player);
player.displayStatus();
// 玩家眩晕
player.applyStun();
player.displayStatus();
// 眩晕状态下的行动
player.move();
player.attack(enemy);
// 更新状态(模拟回合进行)
console.log('\n=== 回合更新 ===');
for (let i = 0; i < 3; i++) {
console.log(`\n--- 第 ${i + 1} 回合 ---`);
player.update();
enemy.update();
player.displayStatus();
enemy.displayStatus();
}
// 隐身状态
player.turnInvisible();
player.displayStatus();
player.move();
player.useSkill('隐身术');
player.attack(enemy); // 隐身攻击
enemy.displayStatus();
player.displayStatus();关键要点
状态封装:将每个状态的行为封装在独立的类中,符合单一职责原则。
状态转换:状态之间的转换由具体状态类控制,而不是由环境类控制。
消除大量条件语句:避免使用大量的if-else或switch语句来处理不同状态的行为。
易于扩展:增加新的状态只需要创建新的状态类,无需修改现有代码。
与其他模式的关系
- 与策略模式区别:策略模式是让客户端选择算法,而状态模式是状态对象自己决定状态转换。
- 与有限状态机结合:状态模式是面向对象的有限状态机实现方式。
- 与单例模式结合:如果状态对象没有实例变量,可以使用单例模式共享状态实例。
优缺点
优点:
- 消除复杂的条件分支语句:将各种状态的逻辑分布到不同的状态类中。
- 易于增加新的状态:增加新状态只需要创建新的状态类,符合开闭原则。
- 状态转换明确:状态转换逻辑封装在状态类中,转换更加清晰。
- 更好的可维护性:每个状态的逻辑独立,便于维护和测试。
缺点:
- 增加类的数量:每增加一个状态就需要增加一个类,可能导致类膨胀。
- 状态逻辑分散:状态逻辑分布在多个类中,可能增加理解难度。
- 对象开销:频繁的状态切换可能产生较多的对象创建和销毁开销。
状态模式在处理复杂的对象状态转换和行为变化时非常有用,特别是在游戏开发、工作流引擎、协议处理等领域有广泛应用。通过合理使用状态模式,可以使代码更加清晰、可维护和可扩展。