1.前言
在现实生活中,人都有高兴和伤心的时候,不同的时候有不同的行为,有状态的对象编程中高兴,伤心可以看成一种状态,传统的解决方案是:人的不同时候的行为都要考虑到的话,然后使用 if-else 或 switch-case 语句来做状态判断,再进行不同情况的处理。这样代码会会过于臃肿,可读性差,且不具备扩展性,维护难度也大。且增加新的状态时要添加新的 if-else 语句,这违背了“开闭原则”,不利于程序的扩展。
2.概念
状态模式的定义:对有状态的对象,把复杂的“判断逻辑”提取到不同的状态对象中,允许状态对象在其内部状态发生改变时改变其行为。状态模式的思想是:当控制一个对象状态转换的条件表达式过于复杂时,把相关“判断逻辑”提取出来,用各个不同的类进行表示,系统处于哪种情况,直接使用相应的状态类对象进行处理,这样能把原来复杂的逻辑判断简单化,消除了 if-else、switch-case 等冗余语句,代码更有层次性,并且具备良好的扩展力。
3.模式的结构和实现
状态模式把受环境改变的对象行为包装在不同的状态对象里,其目的是让一个对象在其内部状态改变的时候,其行为也随之改变。现在我们来分析其基本结构和实现方法。
3.1 模式的结构
状态模式包含以下主要角色。
- 环境类(Context)角色:也称为上下文,相当是所有状态的管理者(manage)并负责具体状态的切换。
- 抽象状态(State)角色:定义一个接口,用以封装环境对象中的特定状态所对应的行为,可以有一个或多个行为。
- 具体状态(Concrete State)角色:实现抽象状态所对应的行为,并且在需要的情况下进行状态切换。
3.2模式的实现
场景:这个场景大家都很熟悉,鼠标进去某个元素,会有个高亮的样式,当鼠标点击这个元素的时候,会有个选中的样式,再次点击选中状态样式消失,当鼠标离开元素,元素没有样式
简单分析下,这里的话会有4个状态,正常状态(无样式),高亮状态(悬浮样式),选中状态(选中样式),高亮选中状态(悬浮加选中样式),并且一个状态进入另一个状态是有条件的,看下图
按需求画出类图
1.首先创建一个抽象类ElementStatus
export abstract class ElementStatus {
public statusName: string;
public abstract calculateState(eventName: string):ElementStatus;
}
2.分别创建ElementNormalState类(正常状态),ElementHightLightState类(高亮状态),ElementHightLightSelectState类(高亮选中状态),ElementSelectState类(选中状态)和构建这些状态的StateFactory类(工厂)
//ElementNormalState.ts 正常状态
import { ElementStatus } from "./ElementStatus";
import { StateFactory } from "./StateFactory";
export class ElementNormalState extends ElementStatus {
public statusName: string;
constructor() {
super();
this.statusName = "正常状态";
}
public calculateState(eventName: string):ElementStatus {
if (eventName == "mouseEnter") {
return StateFactory.getElementStatus("ElementHightLightState");
}
throw new Error("现在是正常状态,切换不了其他状态");
}
}
//ElementHightLightState.ts 高亮状态
import { ElementStatus } from "./ElementStatus";
import { StateFactory } from "./StateFactory";
export class ElementHightLightState extends ElementStatus {
public statusName: string;
constructor() {
super();
this.statusName = "高亮状态";
}
public calculateState(eventName: string): ElementStatus {
if (eventName == "mouseLeaver") {
return StateFactory.getElementStatus("ElementNormalState");
} else if (eventName == "mouseClick") {
return StateFactory.getElementStatus("ElementHightLightSelectState");
}
throw new Error("现在是高亮状态,切换不了其他状态");
}
}
//ElementHightLightSelectState.ts 高亮选中状态
import { ElementStatus } from "./ElementStatus";
import { StateFactory } from "./StateFactory";
export class ElementHightLightSelectState extends ElementStatus {
public statusName: string;
constructor() {
super();
this.statusName = "高亮选中状态";
}
public calculateState(eventName: string): ElementStatus {
if (eventName == "mouseLeaver") {
return StateFactory.getElementStatus("ElementSelectState");
} else if (eventName == "mouseClick") {
return StateFactory.getElementStatus("ElementHightLightState");
}
throw new Error("现在是高亮选中状态,切换不了其他状态")
}
}
//ElementSelectState.ts 选中状态
import { ElementStatus } from "./ElementStatus";
import { StateFactory } from "./StateFactory";
export class ElementSelectState extends ElementStatus {
public statusName: string;
constructor() {
super();
this.statusName = "选中状态";
}
public calculateState(eventName: string): ElementStatus {
if (eventName == "mouseEnter") {
return StateFactory.getElementStatus("ElementHightLightSelectState");
}
throw new Error("现在是选中状态,切换不了其他状态");
}
}
//StateFactory.ts 工厂类
import { ElementHightLightSelectState } from "./ElementHightLightSelectState";
import { ElementHightLightState } from "./ElementHightLightState";
import { ElementNormalState } from "./ElementNormalState";
import { ElementSelectState } from "./ElementSelectState";
export class StateFactory {
public static getElementStatus(statusName: string): Status.ElementStatus {
switch (statusName) {
case "ElementNormalState":
return new ElementNormalState();
case "ElementHightLightState":
return new ElementHightLightState();
case "ElementHightLightSelectState":
return new ElementHightLightSelectState();
case "ElementSelectState":
return new ElementSelectState();
default:
return new ElementNormalState();
}
}
}
3.创建ElementStatusContext类 管理这些状态的类,设置默认状态是正常状态
//ElementStatusContext.ts
import { ElementNormalState } from "./ElementNormalState";
export class ElementStatusContext{
public currentStatus: Status.ElementStatus;
constructor() {
this.currentStatus = new ElementNormalState();
}
public getStatus() {
return this.currentStatus
}
public setStatus(status: Status.ElementStatus) {
this.currentStatus = status;
}
public changeStatus(eventName: string): Status.ElementStatus {
return this.currentStatus.calculateState(eventName);
}
}
//Status.d.ts ts声明文件
declare namespace Status {
export type ElementStatus =import ("./ElementStatus").ElementStatus;
export type ElementStatusContext =import ("./ElementStatusContext").ElementStatusContext;
}
4.最后IndexController 通过事件绑定好这些状态并显示在UI界面上
//Index.controller.ts
import angular from "angular";
import { ElementStatusContext } from "./ElementStatusContext";
export class IndexController implements angular.IController {
public static ElementStatusContext: Status.ElementStatusContext;
public statusName: string;
constructor() {
IndexController.ElementStatusContext = new ElementStatusContext();
this.statusName = IndexController.ElementStatusContext.getStatus().statusName;
}
//鼠标进入事件
public mouseEnterEvent(): void {
let status = this.calAndSetStatus(IndexController.ElementStatusContext, "mouseEnter");
this.statusName = status.statusName
}
//鼠标离开事件
public mouseLeaveEvent(): void {
let status = this.calAndSetStatus(IndexController.ElementStatusContext, "mouseLeaver");
this.statusName = status.statusName
}
//colorElement点击事件
public colorElementClickEvent(): void {
let status = this.calAndSetStatus(IndexController.ElementStatusContext, "mouseClick");
this.statusName = status.statusName
}
//计算并设置状态
private calAndSetStatus(StatusContext: Status.ElementStatusContext, eventName: string) {
let status = StatusContext.changeStatus(eventName);
StatusContext.setStatus(status);
return status;
}
}
下面是 index.html ui界面
<!DOCTYPE html>
<html lang="en" ng-app="myApp">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<style>
.element {
width: 200px;
height: 200px;
border: 5px solid #ccc;
margin: 0 auto;
line-height: 200px;
text-align: center;
font-size: 20px;
}
.hight-light {
background-color: greenyellow;
}
.select {
border-color: green;
}
.hight-light-select {
background-color: greenyellow;
border-color: green;
}
</style>
<body ng-controller="IndexController as vm">
<div class="element" ng-mouseenter="vm.mouseEnterEvent()" ng-mouseleave="vm.mouseLeaveEvent()"
ng-click="vm.colorElementClickEvent()" ng-class="{'hight-light':vm.statusName=='高亮状态','select':vm.statusName=='选中状态','hight-light-select':vm.statusName=='高亮选中状态'}">{{vm.statusName}}</div>
</body>
</html>
最后看下效果图:
总结优缺点
优点:
结构清晰,状态模式将与特定状态相关的行为局部化到一个状态中,并且将不同状态的行为分割开来,满足“单一职责原则”。
将状态转换显示化,减少对象间的相互依赖。将不同的状态引入独立的对象中会使得状态转换变得更加明确,且减少对象间的相互依赖。
状态类职责明确,有利于程序的扩展。通过定义新的子类很容易地增加新的状态和转换。
缺点:
状态模式的使用必然会增加系统的类与对象的个数。
状态模式的结构与实现都较为复杂,如果使用不当会导致程序结构和代码的混乱。
状态模式对开闭原则的支持并不太好,对于可以切换状态的状态模式,增加新的状态类需要修改那些负责状态转换的源码,否则无法切换到新增状态,而且修改某个状态类的行为也需要修改对应类的源码。