状态模式是一种非常常用的设计模式,在JavaScript中也有广泛应用。它的作用是通过将对象的行为与状态分离,使得在不同状态下对象的行为可以发生改变。
以下是一个简单的例子,通过状态模式实现一个电梯的状态转换。
首先,我们定义一个电梯类,其中包含电梯的各种状态和状态转换的方法。
```javascript
class Elevator {
constructor() {
this.state = new CloseState(this);
}
open() {
this.state.open();
}
close() {
this.state.close();
}
run() {
this.state.run();
}
stop() {
this.state.stop();
}
setState(state) {
this.state = state;
}
}
```
接下来,我们定义电梯的各个状态类,它们分别对应电梯的打开、关闭、运行和停止状态。
```javascript
class OpenState {
constructor(elevator) {
this.elevator = elevator;
}
open() {
console.log('电梯门已经打开');
}
close() {
console.log('关闭电梯门');
this.elevator.setState(new CloseState(this.elevator));
}
run() {
console.log('电梯门还没关,不能运行');
}
stop() {
console.log('电梯门还没关,不能停止');
}
}
class CloseState {
constructor(elevator) {
this.elevator = elevator;
}
open() {
console.log('打开电梯门');
this.elevator.setState(new OpenState(this.elevator));
}
close() {
console.log('电梯门已经关闭');
}
run() {
console.log('开始运行');
this.elevator.setState(new RunState(this.elevator));
}
stop() {
console.log('电梯已经停止');
this.elevator.setState(new StopState(this.elevator));
}
}
class RunState {
constructor(elevator) {
this.elevator = elevator;
}
open() {
console.log('电梯正在运行,不能打开门');
}
close() {
console.log('电梯正在运行,不能关闭门');
}
run() {
console.log('电梯正在运行');
}
stop() {
console.log('电梯已经停止');
this.elevator.setState(new StopState(this.elevator));
}
}
class StopState {
constructor(elevator) {
this.elevator = elevator;
}
open() {
console.log('打开电梯门');
this.elevator.setState(new OpenState(this.elevator));
}
close() {
console.log('电梯门已经关闭');
this.elevator.setState(new CloseState(this.elevator));
}
run() {
console.log('开始运行');
this.elevator.setState(new RunState(this.elevator));
}
stop() {
console.log('电梯已经停止');
}
}
```
最后,我们可以通过以下代码使用电梯类。
```javascript
const elevator = new Elevator();
elevator.run(); // 输出:电梯门还没关,不能
接下来我们可以看一个例子,通过状态模式来实现一个订单状态的流转过程。假设订单可以有三种状态:待支付、已支付、已发货。订单的状态可以根据用户的行为来流转,比如用户支付了订单,订单就从待支付状态变成已支付状态;如果商家发货了,订单就从已支付状态变成已发货状态。
首先我们定义订单状态的接口:
```javascript
class OrderState {
constructor(order) {
this.order = order;
}
getName() {}
cancel() {}
ship() {}
}
```
这个接口定义了三个方法,分别是 `getName`、`cancel`、`ship`。这些方法在不同的状态下可能会有不同的实现。接下来我们定义具体的状态类。
```javascript
class WaitingForPaymentState extends OrderState {
constructor(order) {
super(order);
}
getName() {
return 'waitingForPayment';
}
cancel() {
console.log('Cancelling order...');
this.order.setState(new CancelledState(this.order));
}
ship() {
console.log('Cannot ship order when payment is not received.');
}
}
class PaidState extends OrderState {
constructor(order) {
super(order);
}
getName() {
return 'paid';
}
cancel() {
console.log('Cancelling order...');
this.order.setState(new CancelledState(this.order));
}
ship() {
console.log('Shipping order...');
this.order.setState(new ShippedState(this.order));
}
}
class ShippedState extends OrderState {
constructor(order) {
super(order);
}
getName() {
return 'shipped';
}
cancel() {
console.log('Cannot cancel shipped order.');
}
ship() {
console.log('Cannot ship order again.');
}
}
class CancelledState extends OrderState {
constructor(order) {
super(order);
}
getName() {
return 'cancelled';
}
cancel() {
console.log('Cannot cancel cancelled order.');
}
ship() {
console.log('Cannot ship cancelled order.');
}
}
```
这里定义了四个具体的状态类,分别是待支付状态、已支付状态、已发货状态和已取消状态。每个具体状态类都实现了 `getName`、`cancel`、`ship` 方法,根据状态的不同有不同的实现。
最后我们定义订单类:
```javascript
class Order {
constructor() {
this.state = new WaitingForPaymentState(this);
}
setState(state) {
this.state = state;
}
cancel() {
this.state.cancel();
}
ship() {
this.state.ship();
}
}
```
这个类持有当前订单的状态对象,提供了两个方法 `cancel` 和 `ship`,通过调用状态对象的对应方法实现状态的流转。
使用:
```javascript
const order = new Order();
console.log(`Order is in ${order.state.getName()} state.`); // Order is in waitingForPayment state.
order.cancel(); // Cancelling order...
console.log(`Order is in ${order.state.getName()} state.`); // Order is in cancelled state.
order.ship(); // Cannot ship cancelled order.
```
在状态模式中,我们通常使用一个Context(上下文)对象来控制状态的转换,这个Context对象包含一个State对象,用于表示当前的状态,并且包含了一些可以触发状态转换的方法。当我们调用这些方法时,Context对象会根据当前的状态来执行相应的操作,并且根据一定的规则将状态转换成其他状态。
状态模式的核心是状态的抽象和封装,通过将状态的具体实现封装在State对象中,我们可以使Context对象变得简单和易于维护。另外,状态模式也很好地支持了开放-封闭原则,当我们需要新增一种状态时,只需要添加一个新的State子类即可,不需要修改Context类的代码。
下面是一个简单的状态模式的示例,假设我们正在编写一个交通信号灯程序,可以在红、绿、黄三种状态之间进行切换:
```javascript
// 定义一个状态接口
class State {
constructor(context) {
this.context = context;
}
// 定义在该状态下执行的方法
handle() {}
}
// 红灯状态
class RedState extends State {
constructor(context) {
super(context);
}
handle() {
console.log("红灯停");
// 状态转换为绿灯状态
this.context.setState(new GreenState(this.context));
}
}
// 绿灯状态
class GreenState extends State {
constructor(context) {
super(context);
}
handle() {
console.log("绿灯行");
// 状态转换为黄灯状态
this.context.setState(new YellowState(this.context));
}
}
// 黄灯状态
class YellowState extends State {
constructor(context) {
super(context);
}
handle() {
console.log("黄灯等");
// 状态转换为红灯状态
this.context.setState(new RedState(this.context));
}
}
// 定义一个Context类来管理状态
class TrafficLight {
constructor() {
// 初始状态为红灯状态
this.state = new RedState(this);
}
// 执行当前状态的方法
handle() {
this.state.handle();
}
// 设置新的状态
setState(state) {
this.state = state;
}
}
// 使用示例
const trafficLight = new TrafficLight();
trafficLight.handle(); // 红灯停
trafficLight.handle(); // 绿灯行
trafficLight.handle(); // 黄灯等
trafficLight.handle(); // 红灯停
```
在上面的示例中,我们使用State接口来表示状态,并在每个具体的状态类中实现了handle()方法,用于表示在该状态下执行的操作。Context类中包含了当前的状态和执行状态转换的方法setState(),当我们调用handle()方法时,会根据当前的状态来执行相应的操作,并根据一定的规则将状态转换成其他状态。
在状态模式中,状态对象通常是通过工厂方法创建的,以确保每个状态对象都是唯一的。此外,在状态模式中,将状态对象作为参数传递给上下文对象,以确保上下文对象只是在其内部状态改变时发生行为。这种设计模式的主要优点是它在任何时候都允许向对象状态的转换,减少了具体类之间的耦合,并有助于减少 if/else 语句的使用。