状态模式(State)-七十二变

设计模式文章陆续更新

java单例模式
java代理模式
java工厂模式

状态模式(State)-老孙七十二变

俺老孙来也...我是如来佛祖玉皇大帝观音菩萨指定取西经特派使者花果山水帘洞美猴王齐天大圣孙悟空啊!老孙的必杀技就是七十二变,没错比老外的变相怪杰还有牛逼许多.

关于老孙我以前的光辉史,长得帅的都知道,什么长生不老之术,上天下地...憋提了,憋提了那都是过去,到了21世纪,老孙已经不叫什么齐天大圣了,只是小公司里的一只程序猿,有事没事敲敲代码压压惊.

好了不扯,咱们进入主题...[斜眼笑]
其实也没走题,讲的还是齐天大圣,看看他的特殊技能七十二变,如果看成是一个项目里需求,我们如果要完成的话,那岂不是要做72次判断和每次变身的处理.

img-w400

分析72变

对于72变的学术研究就不太深入了,简单描述变法大致分为水陆空这几种方式.

    • 水上:小船..
    • 水下:鱼儿..
    • 陆上:猴子..
    • 陆下:蚯蚓..
    • 空中:小鸟..
    • 空外:飞机..

估计我写的是假的悟空,还有变蚯蚓的,估摸大圣会吐槽我的.没关系,这不是重点,关键的是当我们如果要写下这段代码逻辑的话会是怎样?

我们先看看普通方式的写法.

public class TestClient {

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        System.out.println("请告诉悟空想变的东西...");
        String things = sc.next();
        System.out.println("悟空准备变: " + things);

        // 处理变身的方法
        handleState(things);
    }

    private static void handleState(String things) {
        // 水里的类型
        if ("小船".equals(things) || "鱼儿".equals(things)) {
            // 水上
            if ("小船".equals(things)) {
                System.out.println("悟空变水面上的.." + things);
                // 水下
            } else if ("鱼儿".equals(things)) {
                System.out.println("悟空变水里的.." + things);
            }

            // 陆地的类型
        } else if ("猴子".equals(things) || "蚯蚓".equals(things)) {
            if ("猴子".equals(things)) {
                System.out.println("悟空变陆地上的.." + things);
            } else if ("蚯蚓".equals(things)) {
                System.out.println("悟空变陆地下的.." + things);
            }

            // 天空的类型
        } else if ("小鸟".equals(things) || "飞机".equals(things)) {
            if ("小鸟".equals(things)) {
                System.out.println("悟空变天空中的.." + things);
            } else if ("飞机".equals(things)) {
                System.out.println("悟空变天空外的.." + things);
            }

            // 水陆空之外的范围变不了
        } else {
            System.out.println("变身失败...无此状态:" + things);
        }
    }
}

输出结果:
请告诉悟空想变的东西...
鱼儿
悟空准备变: 鱼儿
悟空变水里的..鱼儿

代码的坏味道

有没看到handleState(state)这个方法实在太长了,如果悟空后面又学习了新的变法,可以变水,变苹果等之类的呢,那这个方法不是要逆天啦.在<重构>中提过,过长的方法会有坏味道.面向对象的设计就是希望做到代码的责任分解,因此对于这种条件判断if-else,switch过于冗杂的逻辑,可以使用状态设计模式来解决.

来一点理论

理论说多了会困,少许理论也可怡情.

<大话设计模式>说到状态模式主要解决的是当控制一个对象状态转换条件表达式过于复杂的情况.把状态的判断逻辑转移到表示不同状态的一系列类当中,可以把复杂的判断逻辑简化.

通过状态模式来优化代码

说了那么多那么,那到底使用状态模式优化后效果如何呢,下面就是见证奇迹的时候..

  • 每个状态的接口
/**
 * 悟空变身后的不同状态的接口
 * 
 * @author relicemxd
 *
 */
public interface State {
     
    /**
     * 处理变身状态
     */
    void handleState();
}
  • 接口的具体实现类.
    陆地状态的实现类,这里我定义了两种类型.
/*
 * 悟空变身的陆地状态类
 */
public class LandState implements State {
    public static final String MONKEY_STATE = "猴子";
    public static final String EARTHWORM_STATE = "蚯蚓";

    private String state;

    /*
     * 要陆地上的什么动物需要告诉悟空
     */
    public LandState(String state) {
        this.state = state;
    }

    /*
     * 变身的方法,由悟空来调用.
     * 悟空知道要变的类型才可以变身成功
     */
    @Override
    public void handleState() {
        if (MONKEY_STATE.equals(state)) {
            System.out.println("变 猴子....");
        } else if (EARTHWORM_STATE.equals(state)) {
            System.out.println("变 蚯蚓....");
        }else{
            //如果不符合条件则变身失败.
            System.out.println("变身失败...");
        }
    }
}

海里状态的实现类,这里我也定义了两种类型.

/*
 * 悟空变身的海里状态类
 */
public class SeaState implements State {
    public static final String SHIP_STATE = "小船";
    public static final String FISH_STATE = "鱼儿";

    private String state;

    public SeaState(String state) {
        this.state = state;
    }

    @Override
    public void handleState() {
        if (FISH_STATE.equals(state)) {
            System.out.println("变 鱼儿....");
        } else if (SHIP_STATE.equals(state)) {
            System.out.println("变 小船儿....");
        }else{
            //如果不符合条件则变身失败.
            System.out.println("变身失败...");
        }
    }
}

空中状态的实现类,这里我也定义了两种类型.

/**
 * 悟空变身的天空状态类
 *
 */
public class SpaceState implements State {
    public static final String BIRD_STATE = "鸟";
    public static final String AIRPLANE_STATE = "飞机";
    
    private String state;

    public SpaceState(String state) {
        this.state = state;
    }
    
    @Override
    public void handleState() {
        if (BIRD_STATE.equals(state)) {
            System.out.println("变 小鸟....");
        } else if (AIRPLANE_STATE.equals(state)) {
            System.out.println("变 飞机....");
        }else{
            //如果不符合条件则变身失败.
            System.out.println("变身失败...");
        }
    }
}
  • 处理不同state的管理类,维护着SeaState,LandState,SpaceState三个状态的操作.
/*
 * 这个Context类相当于 悟空
 * 
 */
public class Context {
    private State state;

    /**
     * 悟空会根据你不同的状态而做不同的处理,
     * 而处理的东西都可以在实现类里完成,如在变小船状态时做什么.
     * 这样就便于拓展了.
     * @param state
     */
    public void setState(State state) {
        this.state = state;
        System.out.println("悟空准备开始变身..");
        
        //悟空可以调用该变身方法
        state.handleState();
    }
}

显然上面的类多了很多,但是每个类里处理的行为也清晰了很多.有取也有舍.

如何使用呢?

对这个这些变身的行为进行归类和封装后,我们就可以在Client中很方便的去调用了.

public class Client {

    public static void main(String[] args) {
         //Context 悟空类
        Context context = new Context();
        
        //陆地状态切换
        //告诉悟空你想让他变成陆地上的蚯蚓.
        LandState landState = new LandState(LandState.EARTHWORM_STATE);
        //悟空得到需求准备变身..
        context.setState(landState);
        
       //水里状态切换
        SeaState seaState = new SeaState(SeaState.SHIP_STATE);
        context.setState(seaState);
        
        //空中状态切换
        //此时我想让悟空变成空中的小船,因此会变身失败.信息来源不对
        SpaceState spaceState = new SpaceState(SeaState.SHIP_STATE);
        context.setState(spaceState);
    }
}

状态模式的组成
上下文类(Context): 在这里他就相当于悟空,可以在该类中定义一些悟空需要的行为.该类的主要任务是维护State接口的一些子类的实例,如LandState.抽象状态类(State): 所有状态的共性接口,封装了与Context相关的一些状态行为,如handleState(state).具体状态类(LandState): State接口的具体实现,对不同状态的行为处理子类.

看下UML类图

img-w400

应用场景

  • 电梯运行状态
  • 维修,正常,自动关门,自动开门,消防..
  • 红绿灯状态
  • 红灯,绿灯,黄灯
  • 订单状态
  • 下单
  • 已付款
  • 已发货
  • 送货中
  • 已收货
  • 售后

对于应用场景还有很多,只要你觉得一个实体有多个状态需要处理的,就可以通过state模式来设计代码.

总结

  • 状态模式的优点:

  • 意图清晰
    将每一个状态转换和动作封装到一个类中,就把着眼点从执行状态提高到整个对象的状态.

  • 代码结构化
    State模式提供了一个更好的方法来组织与特定状态相关的代码.

  • 便于维护
    State模式将if else语句或switch case语句进行了封装,这样对于后续的增删改查从显示的处理数据进而转变成隐式的处理State的子类,减低了复杂逻辑的维护成本.

  • 状态模式的缺点:

  • 增加系统类和对象的个数,这是大多数设计模式的缺点.

  • 对于代码不规范的同学,如果使用不当将导致程序结构和代码的混乱,主要是State模式结构与实现都较为复杂.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容

  • 1 场景问题# 1.1 实现在线投票## 考虑一个在线投票的应用,要实现控制同一个用户只能投一票,如果一个用户反复...
    七寸知架构阅读 1,937评论 7 53
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,799评论 25 707
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,633评论 18 139
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 31,598评论 18 399
  • 写作,就是脱光衣服,在太平洋中间裸泳,与孤独为友,与时间为敌。 ...
    股市V神阅读 1,444评论 5 19