定义:允许对象在内部状态改变时改变它的行为,对象看起来就好像修改了它的类
简单来说就是将状态的行为封装到各个状态类内部,在内部状态改变时,通过委托不同的状态类改变对象的行为,从外部看起来就像是重新初始化了一个类
下面举一个例子来看
之前看过一个html解析的处理,核心处理过程如下
switch(state) {//we are in an unknown state before there's actual content
caseUNKNOWN:if (character == '
saveState(TEXT);
state=TAG_ENCOUNTERED;
}break;//we can encounter any content
caseTEXT:if (character == '
flush();
saveState(state);
state=TAG_ENCOUNTERED;
}else if (character == '&') {
saveState(state);
entity.setLength(0);
state=ENTITY;
}else if (Character.isWhitespace((char) character) && character != 12288) { //全角空格在浏览器中会被解释成汉字,不会被识别成分隔符,按照实际空格数显示
if(nowhite)
text.append((char) character);
nowhite= false;
}else{
text.append((char) character);
nowhite= true;
}break;//we have just seen a < and are wondering what we are looking at//, , , etc.
caseTAG_ENCOUNTERED:
initTag();if (character == '/') {
state=IN_CLOSETAG;
}else if (character == '?') {
restoreState();
state=PI;
}else{
text.append((char) character);
state=EXAMIN_TAG;
}break;//we are processing something like this .//It could still be a or something.
caseEXAMIN_TAG:if (character == '>') {
doTag();
processTag(true);
initTag();
state=restoreState();
}else if (character == '/') {
state=SINGLE_TAG;
}else if (character == '-' && text.toString().equals("!-")) {
flush();
state=COMMENT;
}else if (character == '[' && text.toString().equals("![CDATA")) {
flush();
state=CDATA;
}else if (character == 'E' && text.toString().equals("!DOCTYP")) {
flush();
state=PI;
}else if (Character.isWhitespace((char) character)) {
doTag();
state=TAG_EXAMINED;
}else{
text.append((char) character);
}break;
}
上面只截取其中一段,后面还有很多case,看着就不美观,而且不易扩展
下面使用状态模式看看能不能实现类似的html解析逻辑
对于一段简单的html,像"
test
"这样的html字符串,我们需要解析出标签内部的数据,可以在遍历字符串中的每个字符的时候设置不同的状态,并且触发不同状态的行为
首先需要定义一个State接口,里面有开始标签、结束标签、关闭标签和设置Context这几种方法,然后定义4种状态实现(NoState, CloseTagState, EndTagState, StartTagState)
NoState一般是还未解析html之前的状态,它的下一个状态只能是开始标签状态, 开始标签状态的下一个状态只能是关闭标签状态, 关闭标签状态的下一个状态是结束标签状态
图示:
类结构图如图所示:
Context类:
public classSimpleXmlParserContext {public static State startTagState = newStartTagState();public static State closeTagState = newCloseTagState();public static State noTagState = newNoState();public static State endTagState = newEndTagState();private State state =noTagState;private StringBuffer tempString = newStringBuffer();publicStringBuffer getTempString() {returntempString;
}public void flush(booleanflush){if(flush){
System.out.print(tempString.toString()+"\n");
}
tempString.setLength(0);
}publicState getState() {returnstate;
}public voidsetState(State state) {this.state =state;this.state.setContext(this);
}public voidstartTag() {this.state.startTag();
}public voidendTag() {this.state.endTag();
}public voidcloseTag() {this.state.closeTag();
}public static voidmain(String [] args){
String html= "
<123>
";
SimpleXmlParserContext context= newSimpleXmlParserContext();
context.setState(noTagState);for(int a = 0; a < html.length(); a++){char c =html.charAt(a);if(String.valueOf(c).equals("
context.startTag();
}else if(String.valueOf(c).equals("/")){
context.endTag();
}else if(String.valueOf(c).equals(">")){
context.closeTag();
}else{
context.getTempString().append(c);
}continue;
}
context.flush(true);
}
}
State:
public interfaceState {voidstartTag();voidendTag();voidcloseTag();voidsetContext(SimpleXmlParserContext context);
}
AbstractState:
public abstract class AbstractState implementsState {protectedSimpleXmlParserContext context;public voidsetContext(SimpleXmlParserContext context) {this.context =context;
}public voidendTag(){
}
}
StartTagState:
public class StartTagState extendsAbstractState {
@Overridepublic voidstartTag() {//开启标签
System.out.print("开始标签\n");
}public voidendTag(){this.context.setState(SimpleXmlParserContext.endTagState);this.context.endTag();
}
@Overridepublic voidcloseTag() {this.context.setState(SimpleXmlParserContext.closeTagState);this.context.closeTag();
}
}
NoState:
public class NoState extendsAbstractState {
@Overridepublic voidstartTag() {this.context.setState(SimpleXmlParserContext.startTagState);this.context.startTag();
}
@Overridepublic voidcloseTag() {
}
}
EndTagState:
public class EndTagState extendsAbstractState {
@Overridepublic voidstartTag() {
}public voidendTag(){this.context.flush(true);
}
@Overridepublic voidcloseTag() {this.context.setState(SimpleXmlParserContext.closeTagState);this.context.closeTag();
}
}
CloseTagState:
public class CloseTagState extendsAbstractState {private static final List TAGS = new ArrayList();static{
TAGS.add("div");
TAGS.add("p");
}
@Overridepublic voidstartTag() {this.context.setState(SimpleXmlParserContext.startTagState);this.context.startTag();
}public voidendTag(){this.context.setState(SimpleXmlParserContext.endTagState);this.context.endTag();
}
@Overridepublic voidcloseTag() {
System.out.print("结束标签\n");//判断下标签是否合规范
if(!TAGS.contains(this.context.getTempString().toString())){this.context.flush(true);
}else{this.context.flush(false);
}
}
}
执行main可以获取想要得到的解析结果
这样的话可以避免过多的switch case语句,而且也方便扩展,如果想要添加其他状态,添加一个状态实现子类就可以
比如想要过滤掉html字符串中标签是a的内部数据,如果按照之前的写法的话,需要添加一个case ATAG: 并且修改其他和改状态相关的case内部代码
使用状态模式的话可以直接添加一个ATagState子类,
因为只有在closeTag的时候才可以知道具体是什么标签,也就是说CloseTagState的下一个状态也有可能是ATagState
状态流程图改变如下
public class ATagState extendsAbstractState {public voidendTag(){this.context.flush(false);
}public voidcloseTag(){this.context.setState(SimpleXmlParserContext.closeTagState);this.context.closeTag();
}
}
修改ClosetTagState类的closeTag方法
public voidcloseTag() {
System.out.print("结束标签\n");
//a标签的时候切换到ATagStateif(this.context.getTempString().toString().equals("a")){this.context.setState(SimpleXmlParserContext.aTagState);this.context.flush(false);return;
}//判断下标签是否合规范
if(!TAGS.contains(this.context.getTempString().toString())){this.context.flush(true);
}else{this.context.flush(false);
}
}
使用"123123"测试,结果如下:
总结:
状态模式主要的应用场景有:
一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为。
一个操作中含有庞大的多分支结构,并且这些分支决定于对象的状态。
在使用状态模式前需要先理清各个状态之间的关系,制作状态流程图,然后在各个状态内部实现各自的行为