`
iwindyforest
  • 浏览: 230029 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

学习设计模式之State

阅读更多
发现问题:
大家在Coding的时候,有没有用到很多的选择语句?像这样:
if(…)
{
       …
}
else if(…)
{
       …
}
else if(…)
{
       …
}
else
{
       …
}


我是经常碰到,最经常见到的地方就是在struts的业务逻辑――Action的实现的类里, 以前的程序员很牛B,Coding从来都已“just can run ”为最终标准。

下面这个函数:
public ActionForward execute(ActionMapping mapping, ActionForm form,
HttpServletRequest request, HttpServletResponse response) 
         throws java.lang.Exception

的实现,一般都是一个函数里边写2000-3000行代码,从来不关心结构的问题。

       “标准示范”就是刚刚上边列出来的if…else if…else…语句,这样的语句大多被用来判断提交了什么动作,然后处理的。当然,需求是在不断变化中的,每当新加了一个业务的提交处理的时候,怎么办呢?绝大多数人的选择都是在最后一个else if的后面,else的前面,添加一个else if…,^_^,如此处理。长此以往,一段程序或许本来就有n个else if,然后维护又加了n个,本来2000行的函数,搞成了三千行。哈哈,一个函数体三千行!何其壮观也!!!
       因此,我觉得,程序员在写判断语句的时候,用了多个else if就应该小心一点了,这个语句像某位大师所说的,是在代码中加入了“坏味道”,会引起程序变质,成为“腐坏代码”的。

提出问题:
那么,在出现要使用多个判断,并且判断逻辑经常改变的时候的时候,我们如何才能防止代码腐坏呢?
――使用state模式就是一个比较好的方法

State模式介绍:
tate模式属于对象行为型模式,

意图:允许一个对象在其内部状态改变时改变它的行为

适用场景:
1.一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为。
2.一个操作中含有庞大的多分支结构,并且这些分支决定于对象的状态。

以上是出自GoF的DesignPatterns的官方解释,但是归结到平常使用中,我认为最多的适用场景还是替代if…if else…else多分支,实现更灵活的设计,防止代码腐坏。
可以看出:上面提到的“经典示例”就是个典型的第2个场景

State模式示例:

一开始我们有这么个Context类,这个类有个state成员,当state改变时,我们需要把它的改变打印出来。(当然,我们得假设各个打印的实现逻辑是彼此不同的)。

先看一下
常规的实现:
public class ContextOriginal
{
     private String state;
 
     public String getState()
     {
         return state;
     }
 
     public void setState(String state)
     {
         this.state = state;
     }
 
     public void print(String msg)
     {
         System.out.println(msg);
     }
 
     public void printImplFirst()
     {
         this.print("printImplFirst: " + this.state);
     }
 
     public void printImplSecond()
     {
         this.print("printImplSecond: " + this.state);
     }
 
     public void printImplThird()
     {
         this.print("printImplFinal: " + this.state);
     }
 
     public void execute()
     {
         if(this.state != null)
         {
             if(this.state.equals("first"))
             {
                 this.printImplFirst();
             }
             else if(this.state.equals("second"))
             {
                 this.printImplSecond();
             }
             else if(this.state.equals("third"))
             {
                 this.printImplThird();
             }
             else
             {
                 throw new IllegalArgumentException(
                         "illegalArgumentException: this.state = " + this.state);
             }
         }
         else
         {
             throw new NullPointerException("this.state is null");
         }
     }
 } 


这样的实现方法的弊端刚开始已经分析的很清楚了,如果我们需要再加一个状态“ fourth”,则需要再加一个实现“printImplFourth()”,然后在“最后一个else if的后面,else的前面,添加一个else if…”,典型的使代码变质的方法。



那么我们使用State模式来实现吧:

我们首先声明一个抽象类State,以后所有的实现都继承这个类来实现它的handle方法。

我们在Context中加入成员State,表示Context的当前状态,

然后把所有对Context的state的处理都通过继承State抽象类来实现。

下面是类图描述:(从类图上来看,state模式和strategy模式的类图是非常相似的)

(见附件)
State抽象类的代码:
package patterns.state;
import patterns.state.impl.Context;

public abstract class State {
    public abstract void handle(Context context);
    public abstract void changeState(Context context);
}
 
State的几个实现代码:(只写出了一个,其它实现与之类似)
 
package patterns.state.impl;

import patterns.state.State;

public class FirstState extends State {

    @Override
    public void handle(Context context) {
	System.out.println("Current State is : "
		+ context.getSteate().toString());
	this.changeState(context);
    }

    @Override
    public void changeState(Context context) {
	context.setState(new SecondState());

    }

    public String toString() {
	return this.getClass().getSimpleName();
    }

}


package patterns.state.impl;

import patterns.state.State;

public class SecondState extends State {

    @Override
    public void handle(Context context) {
	System.out.println("Current State is : "
		+ context.getSteate().toString());
	this.changeState(context);
    }

    @Override
    public void changeState(Context context) {
	context.setState(new FirstState());
    }

    public String toString() {
	return this.getClass().getSimpleName();
    }
}



Context类的实现代码:
package patterns.state.impl;

import patterns.state.State;

public class Context {
    // context的当前状态
    private State state;

    public Context() {
	this.state = new FirstState();
    }

    public State getSteate() {
	return state;
    }

    public void setState(State state) {
	this.state = state;
    }

    public void execute() {
	this.state.handle(this);
    }
}


好了,现在State模式的实现写完了。
现在我们什么时候想处理state为first的时候,只需要

package patterns.state.test;

import patterns.state.impl.Context;
import patterns.state.impl.FirstState;

public class Test {

    public Test() {
	Context context = new Context();
	context.execute();
	context.setState(new FirstState());
	context.execute();
	context.execute();
	context.execute();
	context.execute();
    }

    public static void main(String[] args) {
	new Test();
    }

}



通过使用了State模式,不管Context的状态改变多少次,添加了多少个状态,我们都不需要修改它的源代码,而只是通过添加新的实现方法就可以搞定了,这样达到了防止“代码腐坏”的目的。

State模式的特点:

State模式允许一个对象基于内部状态而拥有不同的行为

State模式允许使用类代表状态

Context会将行为委托给当前对象

通过将每个状态封装进一个类,我们把以后需要做的任何更改局部化了

状态(state)的转换可以由StateHandler类或者context类来控制

状态处理类(StateHandler)可以被多个Context实例共享。

使用状态模式将会导致设计中类的数目大量增加



需要注意的地方:
1. state之间的转换. State的转换可以在State类内部完成, 但是这样会带来的问题是各个State之间出现了关联, 那么如果一个State本身改变(减少, 增加) 会带来很大影响, 你就有可能看看当前发生改变的state是否也影响到了其它的state的执行. 不过经过跟同事的讨论发现也是在所难免的. 另外一个, state的转换也可以由其它类调用Context的setState()来完成, 感觉这种情况更不可靠, 因为这样的话其他类就必须知道context的特定state, 带来了不必要的耦合, 因此建议对setState()的调用尽量放在Context和State这两处.


2. State的创建和销毁. 两个方法: 1.在context内部创建所有state, 一直维护从不销毁.2.在状态转换的时候才创建, 然后上一个state由JVM自动回收. 主要原则需要看实际情况是Context的State的变化是否频繁, 如果不频繁, 建议用第二种,比较优雅.




总结:

1.尽量不要使用太多分支的语句。
2.如果函数超过一屏,你就需要考虑是否需要把功能分割了。如果你写了个3000行的“超长”的函数,以后维护的人会很不爽,也会遭到bs的。
3.如果不幸遇到必须使用太多分支且分支与类的某一个状态有关且经常改变,那可以考虑使用state模式,防止“腐坏”。
4.牢记OO设计原则:“找出应用中需要变化之处,把它们独立出来,不要和那些不需要变化的代码混在一起”。
 
  • 大小: 8.7 KB
分享到:
评论
29 楼 longrm 2009-01-05  
最常见的就是dao,比如写一个basedao,然后oracledao、msdao。。都继承basedao,在弄个daofactory来生成dao,然后要新增dao的时候只需再加一个继承basedao的类就可以
28 楼 longrm 2009-01-05  
这个不就是用了抽象类和接口吗?state设计模式,貌似很多模式都用到了,寒。。。。
27 楼 iwindyforest 2009-01-05  
skeleton 写道
楼主明显写错了。
state模式的意图就是通过state的多态来消除判断状态的分支,以楼主这个例子,实现类应该是stateFirst,stateSecond,覆盖的父类方法是printImpl:stateFirst.printImpl(){ this.print("printImplFirst: " + this.toString())}等等。
在实现类里再有类似这样的语句  if(state != null && state.equals("first")) 实在是很搞笑的事情。
另外例子也不恰当,state模式一般应用的场景还应该包括各个状态之间的转换,比如业务的状态从stateFirst在excute之后将转换为stateSecond,否则直接用多态就可以了,没必要再维护一个context


状态的转换,就不说了,前面已经讨论过了.

你说的利用state的多态是状态模式消除分支的思想,
昨天看了下GoFBook总算明白了, 这个例子确实有问题....
应该不存在state--> StateHandler 而是State是个抽象类, 然后对应每一个State的子类, 有handle方法...
当然这是GoF给出的标准实现方法...但不是我想表达的思想,
我以后把这个例子重构下吧
26 楼 skeleton 2009-01-05  
楼主明显写错了。
state模式的意图就是通过state的多态来消除判断状态的分支,以楼主这个例子,实现类应该是stateFirst,stateSecond,覆盖的父类方法是printImpl:stateFirst.printImpl(){ this.print("printImplFirst: " + this.toString())}等等。
在实现类里再有类似这样的语句  if(state != null && state.equals("first")) 实在是很搞笑的事情。
另外例子也不恰当,state模式一般应用的场景还应该包括各个状态之间的转换,比如业务的状态从stateFirst在excute之后将转换为stateSecond,否则直接用多态就可以了,没必要再维护一个context
25 楼 zhangsatanyang 2009-01-04  
格外认真的看完之后,发现没什么用!
24 楼 chbest 2008-11-03  
Factroy.getHandler(context.getState());
里面还不是一样要写 if else?
23 楼 sea7 2008-11-03  
个人感觉从你的代码看起来你这应该是vistor pattern,而不是什么state pattern.
vistor pattern : 当新定义一个handler操作时,并不需要更改Context的结构。
而对于state pattern来说,当状态改变时,应该有state自己改变自己的行为而不是由一个handler来改变。
22 楼 WhisperXD 2008-10-25  
渐行渐远 写道
我不懂模式,所以每次做东西都是根据情况选取最合适的方式来设计。后来学了点模式,发现自己不知不觉用了模式,但分不清是哪一个模式,只知道那种情况下最合适。

我觉得懂模式的人往往做设计时总是先想着用什么模式来套用,而不是想着用什么样的设计最合适,陷入了模式的误区。

一点浅见,请大家批评指正


我在刚开始读很多模式书的时候也会这样,不过后来就变了,有本书改变了我的看法,就是<<refactoring to pattern.>>
读过以后我感觉现在更“深入”了些,也会在开始想到可能用到哪些模式,但开始却不去用了。。。。
21 楼 WhisperXD 2008-10-25  
taupo 写道
模式不是万能的
难道为了少写几个ifelse就要用state模式吗
我觉得state模式不过是把if -else语句换成了一个单独的类吧了

当然,维护起来是方便些,不过类大量增加也让人烦

我到有个解决办法。。。。。。。。。。

珍爱生命,远离JAVA  :)

大部分情况下不需要用到模式,我个人现在倾向于xp和重构那一套知识。
但很多时候用模式能够很好的解决问题。比如有30多个if else,里面有n多逻辑和接口调用。我不信一个正常的人能在短时间内明白里面的意思。

而state模式,本身的目的也不是为了替换if-else。。。如果把它理解为简单的替换。。方向就错了。。。
state的理念我认为是状态转换。也就是从某种状态转换为另外一种状态,或者有一套自己的转换过程,比如a->b->c状态,他们内部自己去管理这个状态具体是用哪一个流程来转换。类似一个复杂的开关控制面板系统。或者红绿灯。
20 楼 taupo 2008-10-25  
模式不是万能的
难道为了少写几个ifelse就要用state模式吗
我觉得state模式不过是把if -else语句换成了一个单独的类吧了

当然,维护起来是方便些,不过类大量增加也让人烦

我到有个解决办法。。。。。。。。。。

珍爱生命,远离JAVA  :)
19 楼 reg 2008-10-23  
maxima 写道
lz的方法需要往context写注入hander方法,那就需要改变原来实例属性、
一般不会这么做吧。
如果加入工厂模式来实现比较合理一点。
假设有个Factroy 提供根据state 返回hander类
那么
StateHandler   hander = Factroy.getHandler(context.getState());
hander.handle(context);


实际中,这种用法比较多~

运用state模式可能类更多了,代码也多了,但是程序的维护性和扩展性会更好,特别是在业务越来越复杂的情况下,优势更明显~
18 楼 maxima 2008-10-16  
lz的方法需要往context写注入hander方法,那就需要改变原来实例属性、
一般不会这么做吧。
如果加入工厂模式来实现比较合理一点。
假设有个Factroy 提供根据state 返回hander类
那么
StateHandler   hander = Factroy.getHandler(context.getState());
hander.handle(context);
17 楼 black_zerg 2008-10-15  
看了这个例子,主要的感觉是很麻烦。还是if else 省事。
这个没什么特别要求还是别用了,一堆代码还不好懂。另外那个修改过的版本是比较好的,比较实用的例子
16 楼 Ethan 2008-09-14  
可以打个比方,一个计算器上面有很多个按钮,没个按钮其实就对应一个state,这样每当user按下某个按钮的时候其实已经提交了需要执行的某种state了,然后计算器会根据这个state调用适当的handler来处理请求了。
15 楼 rainerWJY 2008-09-08  
linpyi 写道
不一定是为了使用设计模式而去改变代码,
分支细了有时候可能导致类爆炸

不过写的不错


以前讨论过。如果不在乎那点性能,可以考虑使用InnerClass来解决类爆炸的问题。同时也比较容易规范一些。
14 楼 xiaopang106 2008-09-08  
lz写得很不错啊,我感觉和《headfirst 设计模式》中的第一个例子有点相似,只是这个设计模式好像并不是为了用来解决 if{}else{}的。才刚刚看《headfirst 设计模式》,也不知道对不对 呵呵
13 楼 linpyi 2008-09-08  
不一定是为了使用设计模式而去改变代码,
分支细了有时候可能导致类爆炸

不过写的不错
12 楼 czlonly 2008-09-08  
分支分化成类,类就太多了
11 楼 luckaway 2008-09-06  
建议你去看一下《重构》。
当代码里有多处if else语句的时候。
1:找到动态嵌入点。用state模式或Strategy模式,state通常情况下是无法用的,一般都是用Strategy模式。
2:如果没有动态切入点。如你的ContextOriginal。那就要把if else语句抽取出来放到的一个方法里,而且这个方法除if else没有其他的代码。然后在不同的地方通用它。
3:下面是我重构的代码。我这样修改了对现在没有多大意义,因为你excute()里没有其他业务代码。通常情况是还有其他业务代码的。但是可扩展性增强了

public class ContextOriginal {

	private String state;

	public String getState() {
		return state;
	}

	public void setState(String state) {
		this.state = state;
	}

	public void print(String msg) {
		System.out.println(msg);
	}

	public void printImplFirst() {
		this.print("printImplFirst: " + this.state);
	}

	public void printImplSecond() {
		this.print("printImplSecond: " + this.state);
	}

	public void printImplThird() {
		this.print("printImplFinal: " + this.state);
	}

	public void execute() {
		printImpl(this.state);
	}

	public void printImpl(String state) {
		if (state == null) {
			throw new NullPointerException("this.state is null");
		} else if (this.state.equals("first")) {
			this.printImplFirst();
		} else if (this.state.equals("second")) {
			this.printImplSecond();
		} else if (this.state.equals("third")) {
			this.printImplThird();
		} else {
			throw new IllegalArgumentException("illegalArgumentException: this.state = " + this.state);
		}
	}
}


在coding的时候。不要刻意想着一定要吧某种设计模式给用上。
“设计模式知识OO设计上一部分内容”这句话可能不完全准确。能理解就好
00设计原则
http://samuelray.iteye.com/blog/170463
10 楼 a_nuo 2008-09-06  
写的很好,现在在学设计模式,很有帮助

相关推荐

    设计模式C++学习之状态模式(State)

    设计模式C++学习之状态模式(State)

    轻松掌握state设计模式

    该PDF是我在学习state pattern时所做的笔记。里面包括了state pattern 的定义、何时使用、是否使用及实例四个部分,实例部分写的比较细,看完这个实例,相信大家也就知道怎么在自己的项目中应用state pattern了。这...

    研磨设计模式(完整带书签).part2.pdf

    也可以作为高效学生深入学习设计模式的参考读物! 第1章 设计模式基础 第2章 简单工厂 第3章 外观模式 第4章 适配器模式(Adapter) 第5章 单例模式(Singleton) 第6章 工厂方法模式(Factory Method) 第7章...

    Head First设计模式

    第12章介绍如何将两个以上的设计模式结合起来成为新的设计模式(例如著名的MVC模式),作者称其为复合设计模式(这是作者自创的名称,并非四人组的标准名词),第13章介绍如何进一步学习设计模式,如何发觉新的设计模式...

    head first 设计模式

    第12章介绍如何将两个以上的设计模式结合起来成为新的设计模式(例如著名的MVC模式),作者称其为复合设计模式(这是作者自创的名称,并非四人组的标准名词),第13章介绍如何进一步学习设计模式,如何发觉新的设计模式...

    大优惠Head First 设计模式

    第12章介绍如何将两个以上的设计模式结合起来成为新的设计模式(例如著名的MVC模式),作者称其为复合设计模式(这是作者自创的名称,并非四人组的标准名词),第13章介绍如何进一步学习设计模式,如何发觉新的设计模式...

    HeadFirst设计模式(中文版)

    第12章介绍如何将两个以上的设计模式结合起来成为新的设计模式(例如著名的MVC模式),作者称其为复合设计模式(这是作者自创的名称,并非四人组的标准名词),第13章介绍如何进一步学习设计模式,如何发觉新的设计模式...

    Head First 设计模式(中文版)

    第12章介绍如何将两个以上的设计模式结合起来成为新的设计模式(例如著名的MVC模式),作者称其为复合设计模式(这是作者自创的名称,并非四人组的标准名词),第13章介绍如何进一步学习设计模式,如何发觉新的设计模式...

    HeadFirst 设计模式java源代码

    第12章介绍如何将两个以上的设计模式结合起来成为新的设计模式(例如著名的MVC模式),作者称其为复合设计模式(这是作者自创的名称,并非四人组的标准名词),第13章介绍如何进一步学习设计模式,如何发觉新的设计模式...

    Head First设计模式(中文,无水印,完整版)

    第12章介绍如何将两个以上的设计模式结合起来成为新的设计模式(例如著名的MVC模式),作者称其为复合设计模式(这是作者自创的名称,并非四人组的标准名词),第13章介绍如何进一步学习设计模式,如何发觉新的设计...

    设计模式可复用面向对象软件的基础.zip

    书名: 设计模式可复用面向对象软件的基础 英文原书名: Design Patterns:Elements of Reusable Object-Oriented software 作者: Erich Gamma 等 译者: 李英军 马晓星 蔡敏 刘建中 书号: 7-111-07575-7 页码: 254 定价...

    设计模式 GOF 23

    本书设计实例从面向对象的设计中精选出23个设计模式,总结了面向对象设计中最有价值的经验,并且用简洁可复用的形式表达出来。本书分类描述了一组设计良好,表达清楚的软件设计模式,这些模式在实用环境下有特别有用...

    研磨设计模式(完整带书签).part1.pdf

    也可以作为高效学生深入学习设计模式的参考读物! 第1章 设计模式基础 第2章 简单工厂 第3章 外观模式 第4章 适配器模式(Adapter) 第5章 单例模式(Singleton) 第6章 工厂方法模式(Factory Method) 第7章...

    head first 设计模式-设计模式必读

    第12章介绍如何将两个以上的设计模式结合起来成为新的设计模式(例如著名的MVC模式),作者称其为复合设计模式(这是作者自创的名称,并非四人组的标准名词),第13章介绍如何进一步学习设计模式,如何发觉新的设计模式...

    设计模式--C++

    1.1 什么是设计模式 2 1.2 Smalltalk MVC 中的设计模式 3 1.3 描述设计模式 4 1.4 设计模式的编目 5 1.5 组织编目 7 1.6 设计模式怎样解决设计问题 8 1.6.1 寻找合适的对象 8 1.6.2 决定对象的粒度 9 1.6.3 指定对象...

    Head First 设计模式(中文完整版+附书源码)part1

    第12章介绍如何将两个以上的设计模式结合起来成为新的设计模式(例如著名的MVC模式),作者称其为复合设计模式(这是作者自创的名称,并非四人组的标准名词),第13章介绍如何进一步学习设计模式,如何发觉新的设计...

    设计模式:可复用面向对象软件的基础--详细书签版

     “[设计模式]在实用环境下特别有用,因为它分类描述了一组设计良好,表达清楚的面向对象软件设计模式。整个设计模式领域还很新,本书的四位作者也许已占据了这个领域造诣最深的专家中的半数,因而他们定义模式的方法...

    ASP.NET设计模式-杨明军译(源码)

    《asp.net设计模式》涵盖了开发企业级asp.net应用程序的知名模式和最佳实践。本书用到的模式可以用于从asp.net1.0到asp.net 4.0的任何版本。不必管模式本身所用的语言,可以将模式用于任何面向对象编程语言。  ...

    设计模式(.PDF)

    1.1 什么是设计模式 2 1.2 Smalltalk MVC中的设计模式 3 1.3 描述设计模式 4 1.4 设计模式的编目 5 1.5 组织编目 7 1.6 设计模式怎样解决设计问题 8 1.6.1 寻找合适的对象 8 1.6.2 决定对象的粒度 9 1.6.3 指定对象...

Global site tag (gtag.js) - Google Analytics