状态模式

   简单的状态模式和策略模式很类似,什么是状态模式呢? 
   简单地说就是,在不同的状态下会有不同的行为.
   就拿人来举例吧,一个人的心情不同,就会产生不同的行为;
  1. public class Person {
  2. private Mood mood;
  3. public void setMood(Mood mood) {
  4. this.mood = mood;
  5. }
  6. public void behavior(){
  7. mood.behave();
  8. }
  9. }
在这段代码中,我们通过给一个人设置一个心情来改变他的行为.来实现了心情不同,就会产生不同的行为的状态模式,
但是在现实生活中,心情往往是自己改变的而不是通过外部的设置,为了更加符合面向对象的模式,我们为其设置一个配置文件,通过修改配置文件来改变人的心情.
首先我们先将3种心情存储在一个Map集合中,通过改变配置文件来动态的改变任务的心情;
  1. public class Person {
  2. private Mood CurrentMood;
  3. private Map<String,Mood> moods = new HashMap<String, Mood>();
  4. //初始化普通心情
  5. public Person() {
  6. moods.put("default", new Normal());
  7. }
  8. public void setMood(String moodName,Mood mood) {
  9. moods.put(moodName, mood);
  10. }
  11. public void behavior(){
  12. CurrentMood = getMood();
  13. if(CurrentMood == null){
  14. CurrentMood = moods.get("default");
  15. }
  16. CurrentMood.behave();
  17. }
  18. public Mood getMood(){
  19. Config config = Config.getInstance();
  20. String moodName = config.getProperty("mood");
  21. return moods.get(moodName);
  22. }
  23. }
   我们这样虽然实现了,动态的改变心情,并且使他的行为也随之改变,但是这里有一些问题不知道大家发现了没有?
那就是如果我们写的是一个多线程的的程序,那么这个当前心情就可能随时被其他线程改变,与之而来的的就是一系列线程安全的问题.
    我们怎么来解决这个问题呢?
    很简单,有两种方法:
    1.不将将当前的心情声明成成员变量,而将其声明成为局部变量,只有在函数体内才可以访问,这样就解决了线程安全问题,但是这同时也带来了另外一个问题,如果我需要在该函数体外访问当前的心情,就无法做到了.
    2.为了解决这个问题,我们还是必须把当前的心情设定为全局变量,但是在函数体内在设置一个局部变量当前心情',将当前心情设置为当前心情',通过这个小技巧可以解决线程安全的问题:
  1. public void behavior(){
  2. currentMood = getMood();
  3. if(currentMood == null){
  4. currentMood = moods.get("default");
  5. }
  6. Mood currentMood_ = currentMood;
  7. currentMood_.behave();
  8. }
   这种情况下,因为执行behave()行为的是currentMood_对象,所以如果currentMood发生改变并不会引发混乱.
    但是这只是一种理想的情况,现实情况也有一些状态的变化是存在先后顺序的,比如说,我们的生命周期把,幼年->青年->中年->老年,我们只能按照这个顺序去变化,不可能直接从幼年直接到老年,也不可能从中年再到青年,这需要怎么解决呢?
先看下面这段代码,我在Test()方法中调用了两次person的live()方法,但是人的状态并没有改变,而是打印了两遍我是小孩;
  1. State state = new ChildState();
  2. Person person = new Person(state);
  3. person.live();
  4. person.live();
  1. 我是小孩,过着无忧无虑的童年生活!
  2. 我是小孩,过着无忧无虑的童年生活!
我们怎么解决这个问题呢,显然需要在Person类的live()方法中修改我们的状态,在调用了state的live()方法之后将他切换为下一个状态.具体代码:
  1. public void live(){
  2. state.live();
  3. if( state instanceof ChildState){
  4. state = new YouthState();
  5. }else if(state instanceof YouthState){
  6. state = new MiddleState();
  7. }else if(state instanceof MiddleState){
  8. state = new EldState();
  9. }else if(state instanceof EldState){
  10. System.out.println("我已经死了,没有下一个状态了");
  11. }
  12. }
 我们再来调用Test()方法进行测试! 
  1. State state = new ChildState();
  2. Person person = new Person(state);
  3. person.live();
  4. person.live();
  5. person.live();
  1. 我是小孩,过着无忧无虑的童年生活!
  2. 我是青年,我对未来充满希望!
  3. 我是中年人,我要为全家人而努力打拼!!
   我们发现经成功实现了我们的要求.
   但是我回过头来再看我们的代码,是不是非常丑呀:
   1.如果我们有100个状态,我们是不是需要if else 100次呀,
   2.不知道大家发现没有,我们每次切换状态的时候并不是切换回以前的实例,而是又重新创建了一个实例,原来的的实例都被垃圾回收了.比如说我们需要切换100次状态,我们是不是需要创建100个实例,同时在销毁100个实例,这样是不是非常浪费性能呀.
    我们如何解决呢?
    我们仔细想一想,第一种if else的情况是不是和我们策略模式有点像呢,因为我们的不同的生命周期的状态的切换策略是不一样的,我们可以将切换状态的这个策略交给他持有的对象去实现,而真正的person是并不知道,当前的是哪一种状态,是不是有点像我们的CD机呢? 下面看一下代码实现,有助于我们的理解,我们可以对比CD机一起看.
  1. public class ChildState implements State {
  2. @Override
  3. public void live() {
  4. System.out.println("我是小孩,过着无忧无虑的童年生活!");
  5. }
  6. @Override
  7. public State next() {
  8. return new YouthState();
  9. }
  10. }
在这里,我们为State接口添加了下一个状态的方法next(),我们只需要调用next()方法就可以实现状态的切换,而并不用关心当前究竟是什么状态;
  1. public void live(){
  2. if( state != null){
  3. state.live();
  4. state.next();
  5. }else{
  6. System.out.println("我已经死了,没有下一个状态了");
  7. }
  8. }
第二种问题呢?如何避免我们频繁的创建和销毁对象呢?答案是一个新的知识点--枚举.
我们来看下面的代码:
  1. public enum State {
  2. CHILD(){
  3. @Override
  4. public void live() {
  5. System.out.println("我是小孩,过着无忧无虑的童年生活!");
  6. }
  7. @Override
  8. public State next() {
  9. return YOUNTH;
  10. }
  11. },YOUNTH(){
  12. @Override
  13. public void live() {
  14. System.out.println("我是青年,我对未来充满希望!");
  15. }
  16. @Override
  17. public State next() {
  18. return MIDDLE;
  19. }
  20. },MIDDLE(){
  21. @Override
  22. public void live() {
  23. System.out.println("我是中年人,我要为全家人而努力打拼!!");
  24. }
  25. @Override
  26. public State next() {
  27. return ELD;
  28. }}
  29. ,ELD(){
  30. @Override
  31. public void live() {
  32. System.out.println("我是老年人,我要安享晚年!");
  33. }
  34. @Override
  35. public State next() {
  36. return null;
  37. }
  38. };
  39. public abstract void live();
  40. public abstract State next();
  41. }
  通过枚举,我们完美的解决了切换对象的问题.

  但是呢?有时候我们切换状态并不是像这样有顺序的,而是达到某种条件就动态的切换状态,比如,三峡大坝当水位达到100米的时候就开闸放水,当水位小于80米的时候就关闸储水...等等,这又怎么实现呢?请听下回分解.















原文地址:https://www.cnblogs.com/Jxiaobai/p/6617493.html