重構:從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

最後由於我太墨跡被收銀員小姐姐趕了出去什麼都沒有吃上......

相關文章
相關標籤/搜索