• 状态模式


       简单的状态模式和策略模式很类似,什么是状态模式呢? 
       简单地说就是,在不同的状态下会有不同的行为.
       就拿人来举例吧,一个人的心情不同,就会产生不同的行为;
    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米的时候就关闸储水...等等,这又怎么实现呢?请听下回分解.















  • 相关阅读:
    Tiny64140之初始化时钟
    Tiny6410之控制icache驱动
    Tiny6410之按键裸机驱动
    Linux -- man 、info、 whatis、 -h
    Linux -- which whereis
    Linux -- sudoers (简单:转)
    Linux -- sudo
    Linux -- sudoers文件
    Linux -- cp
    Linux -- mv
  • 原文地址:https://www.cnblogs.com/Jxiaobai/p/6617493.html
Copyright © 2020-2023  润新知