重构:从kfc点单发现状态模式

什么是状态模式

对象的行为依赖于它的状态(属性),容许对象在内部状态发生改变而且能够根据它的状态改变而改变它的相关行为。node

状态模式和策略模式很类似,也是将类的"状态"封装了起来,在执行动做时进行自动的转换,从而实现,类在不一样状态下的同一动做显示出不一样结果。它与策略模式的区别在于,这种转换是"自动","无心识"的,策略模式是在求解同一个问题的多种解法,这些不一样解法之间毫无关联;状态模式则不一样,状态模式要求各个状态之间有所关联,以便实现状态转移。android

正文

国庆回来决定重构我司的重要项目,因其复杂的逻辑一直找不到好的角度去梳理代码,某个夜黑风高的晚上,我来到了kfc,当我看到了菜单中琳琅满目的内容陷入了沉思.....程序员

image

这里是形式代码,不要深究

此时一份点单的心里独白

if (个人钱>50) {
    if (饿) {
      我好富有啊随便点,点2个我最喜欢的原味鸡,再来个套餐
    } else {
      本身喜欢的单品都来同样好了
    }
} else if (30<个人钱<50) {
    if (饿) {
      低于50的套餐
    } else {
      本身喜欢的单品累加不能超过50
    }
} else if (15<个人钱<30) {
    if (饿){
        低于30的套餐
    }else{
        本身喜欢的单品累加不能超过30
    }
    ......
} else if (5<个人钱<15) {
    只能根据价格来不能追求喜爱了..
} else {
    作洗碗工 || 饿肚子
}

复制代码

万一钱的金额或者食物价格(状态)有所变化

1.须要修改购物条件:代码的判断条件可能须要重写npm

2.须要继续深刻购物条件:往深层次加入条件变得复杂冗长设计模式

image

状态模式登场

第一步:抽离状态

状态模式首先须要总结出会影响行为的要素数组

咱们把当前条件列出来咱们用一个状态对象来包装它:bash

{
    hungry: true/false,
    money:  40,
    single:   [ 原味鸡, 鸡米花, 土豆泥, 薯条,汉堡...],
    setMeal:[ 高调奢华套餐(50¥)、性价比套餐(40¥)、无论饱套餐(25¥) ],
}
复制代码

Tips: 实际业务中能够用各类思惟导图总结出当前需求有哪些状态app


第二步:规划配置表

将状态与行为之间的关系封装在一个表,经过状态的变化驱动行为。ui

根据当前状态概括出购买选择的配置表:spa

抽象出俩个单位即手上的钱和自身饥饿状况,同时他们都有本身的触发条件,也就是不一样的状态。

 {
    money: { 
    
        money> 50: [...single, ...setMeal], // 喜好的单品和套餐都能选择
        
        30 < money < 50: [...single, setMeal[0], setMeal[1]], // 价格容许的套餐和全部单品
        
        15 < money < 30: [...single, setMeal[2]], // 价格容许的套餐和全部单品
        
        5 < money < 15: [single[1], single[2], single[3]],// 价格容许的单品
        
        money < 5: 作洗碗工 || 饿肚子,
    },
    hungry: true / false,  //饿就选套餐/不饿选择单品
}

最后的购买行为:当前金钱数量决策出对应价位的菜品,再根据饥饿状况选出套餐仍是单品组合

复制代码

咱们能够把配置表理解成一个状态机,根据状态变化能够影响个人购买行为,若是状态(钱)有了变化(优惠券,地上捡到50元...)也能作出可预知的购买动做(毕竟已经根据钱的多少分好了行为)。

配置表能够在代码的容灾性或是后期多人维护的时候能够更加专一到状态的变化上。


第三步(拓展):拆分更小的粒度

加需求:厨房说原味鸡售罄只有鸡米花了,须要贴出告示,须要将套替中的原味鸡替换成鸡米花。

关于拆分配置的颗粒度,仔细思考外界因素对食品的影响致使购买行为出现变化,是否能够对行为再配置成可配置管理的内容。为何kfc都是一种菜品一个容器?

single:   [ 原味鸡, 鸡米花, 土豆泥, 薯条,汉堡...],
    setMeal:[ 高调奢华套餐(50¥)、性价比套餐(40¥)、不必定管饱套餐(25¥) ],
    
    改造一下总体结构,每个单品都有它的状态,方便咱们根据状态组合(万一需求又改了呢?)
    
    single:   [
       // 想一想为何我要多加价格,万一以后售价(需求)变更了呢?
        {
          name:原味鸡,
          price:12,
          stock:false, // 没货啦
        }, 
        { name:鸡米花,
          price:10,
          stock:true,
        },
        ....
        ....
        ....
    ],
    setMeal:[ 
    // 把菜单和单品之间的联系挂起来
    {
        name:高调奢华套餐:
        price:50,  
        single[0], //原味鸡
        single[1], //鸡米花
        single[2], //土豆泥
        single[3], //薯条
        single[4]  //汉堡
    },{
        name:性价比套餐,
        price:40,
        .... // 单品组合
    }{
        name:无论饱套餐
        price:25
        .... // 单品组合
    } ],
    
    
复制代码

完成需求: 这样子能够直接将原味鸡换成鸡米花

now: single[0] = single[1]
复制代码

案例

我须要重构的项目有不少不一样的单位,他们所展现的表单内容有部分是相同的,有部分是不一样的,这个时候我能够经过状态模式去配置。

原代码:

<el-form-item label="应用名称">
    <el-input v-model="form.name"/>
</el-form-item>
<el-form-item label="发部分支">
    <el-input v-model="form.branch"/>
</el-form-item>
<el-form-item v-if="env" label="发布环境">
    <el-input v-model="form.env"/>
</el-form-item>
<el-form-item v-if="appType==='node'|| appType==='static'" label="npm install">
    <el-input v-model="form.npm"/>
</el-form-item>
<el-form-item v-if="appType==='android'" label="打包加固">
    <el-input v-model="form.package"/>
</el-form-item>
<el-form-item v-if="appType!=='static'&& env!='dev'" label="发布暂停">
    <el-input v-model="form.isStop"/>
</el-form-item>

..... // 省略各类条件
复制代码

改进代码:

1.抽取状态: 

    环境:env:['prod', 'dev', 'test'], 应用:appType:['node','android','static']

2.配置状态机:

    // configState.js
    
    const configState = {
        env:{
            env:['env','dev','test'],
            appType:['node','android','static'],
        },
        npm:{
            env:['env'],
            appType:['node','static'],
        },
        package:{
            env:['env','dev','test'],
            appType:['android'],
        },
         isStop:{
            env:['env','test'],
            appType:['android','node'],
        },
    }
    
    
3.抽象行为:
    
    import { configState } from "./configState";
    
    // 抽象当前展现行为
    
    const state = fn(env, appType) {
        
        /*
        * 此处代码省略!!!
        * 遍历configState,若env和appType数组中均含有则为true
        */
        
        return {
            env:true,
            npm:true,
            package:false,
            isStop:true,
        },
    }
    
    
    <el-form-item label="应用名称">
        <el-input v-model="form.name"/>
    </el-form-item>
    <el-form-item label="发部分支">
        <el-input v-model="form.branch"/>
    </el-form-item>
    <el-form-item v-if="state.env" label="发布环境">
        <el-input v-model="form.env"/>
    </el-form-item>
    <el-form-item v-if="state.npm" label="npm install">
        <el-input v-model="form.npm"/>
    </el-form-item>
    <el-form-item v-if="state.package" label="打包加固">
        <el-input v-model="form.package"/>
    </el-form-item>
    <el-form-item v-if="state.isStop" label="发布暂停">
        <el-input v-model="form.isStop"/>
    </el-form-item>

复制代码

总结

场景

  1. 代码中包含大量与对象状态有关的条件语句如if...else, switch..case等。
  2. 逻辑之间混乱相隔较远须要抽象在一个地方统一管理
  3. 业务条件变更频繁(程序员不背锅)

优势:

  1. 场景变化或是增多,状态切换的逻辑被分布在状态类中经过增长新的状态类,很容易增长新的状态和转换。

  2. 解耦,状态和动做类中的行为能够很是容易地独立变化而互不影响。

  3. 内聚,状态机的存在可让咱们更聚焦在状态的控制上。

缺点:

  1. 有多少的状态就得有多少的单例方法,所以会建立大量的相关行为。

  2. 代码结构变得复杂,须要提供专门的状态机文件。

实现

  1. 状态拥有者的实体模型。

  2. 状态接口(也可以使用抽象类),定义业务方法。

  3. 状态的各个具体实现类,分别实现业务方法。


关键在于抽象状态与行为的联动,总结可变与可变的代码。

全部设计模式都有它在生活中的案例,life is design

最后由于我太墨迹被收银员小姐姐赶了出去什么都没有吃上......

相关文章
相关标签/搜索