記一次策略模式+適配器模式在實際項目中的真實使用


項目背景

涉及業務細節,均已做脫敏處理。

在一個移動端項目中,需要做城市定位功能。這個城市定位一開始沒什么,就是項目初始化的時候調用一下高德的城市定位API,僅此而已。

經過一些業務調整和產品變動,城市定位的需求逐漸變得復雜了起來。比如:內嵌到一個原生APP提供的webview中,需要調用原生方法取APP外層的城市定位;作為url鏈接拋出給第三方使用時,希望鎖定城市,不要去做自動定位。

成功取到city后,會存儲在sessionStorage中,供全局取用。

而且比較麻煩的是:這些定位的字段還不太一樣,比如城市名,有的叫cityName,有的叫chineseCityName,諸如此類。

難以維護的歷史代碼

換了幾波開發團隊后,這部分代碼逐漸演化成了下面這樣:

async function getCity() {
    let city = {};
    
    // 優先從sessionStorage里面取
    city = JSON.parse(sessionStorage.getItem('city'))
    
    // 原生方法獲取城市
    if (!city.cityCode && window.$native.getCity) {
        // xxx
        return {
            cityName: nativeCity.cityName,
            cityCode: nativeCity.cityCode
        }
    }
    
    // 假如url有綁定城市
    if ($route.query.cityCode) {
        // xxx
        return {
            cityName: matchCity.city,
            cityCode: matchCity.areaCode
        }
    }
    
    // 調用高德api定位
    if (!city.cityCode) {
        // xxx
        return {
            cityName: res.chineseCityName,
            cityCode: res.adcode
        }
    }
    
    // 這里已經違反了函數單一職責原則
    // 函數名是getCity,卻做了超越getCity職責的事情
    if (!city.cityCode) {
        $router.replace('/select-city');
    } else {
        sessionStorage.setItem(city)
    }
    
    return city
}

上面這個函數是原函數的簡化版,實際上的原函數代碼比這個要長得多,當這個函數里面有一點點修改,測試都不得不對全部環境回歸:回歸測試APP內打開定位是否正常、回歸測試URL攜帶城市時是否定位正常、回歸測試瀏覽器正常打開定位是否正常。

這就是我當時接鍋這個這個項目的真實現狀:當定位的需求發生更改時,沒人願意去碰這個破函數。然而我卻真的被安排去接這個鍋了orz。

改造前的分析

經過一番思索,我初步決定改造如下:

  • 不同環境context(比如在APP內或者在瀏覽器內),對應的獲取城市策略strategy是不同的,這就是策略模式
  • 要保持函數單一職責原則,一個函數只做一件事
  • 對於不同的字段名,可以通過適配器模式來做到統一
  • 最后輸出給全局用的城市格式應該無論在何種環境下都是一致的

改造后的代碼

這里要說明一下,為了讓結構更清晰,這里省略了很多實現細節,比如try catch的異常捕獲處理,以及一些類型的判斷等等等等
/* * 城市字段適配器 * @params {Object} city 各種字段的城市對象 * @return {Object} 標准統一格式的城市對象 */
const formatCityByAdapter = (city) => {
    return {
        cityName: city.cityName || city.chineseCityName || city.city,
        cityCode: city.cityCode || city.adcode || city.areaCode
    }
}

// 通過高德獲取城市
const getCityFromAMap = async () => {
  // xxx
  return city
}

// 通過APP原生獲取城市
const getCityFromNative = async () => {
    // xxx
    return city
}

// 通過sessionStorage獲取城市
const getCityFromSessionStorage = () => {
    // xxx
    return city
}

// 通過url中獲取城市
const getCityFromUrl = () => {
    // xxx
    return city
}

// 在APP的webview中獲取城市的方法,經確認沒有url固定城市的情況
// 而且如果已經在原生環境中,就不用加一些條件去判斷有沒有原生的方法
const getCityInAPP = async () => {
    const city = getCityFromSessionStorage() || await getCityFromNative()
    return formatCityByAdapter(res)
}

// 在瀏覽器中獲取城市的方法,可能存在有url固定城市的情況
const getCityInBrowser = async () => {
    const city = getCityFromSessionStorage() || getCityFromUrl() || await getCityFromAMap()
    return formatCityByAdapter(res)
}

// 獲取環境信息,先確定環境
const getEnv = () => {
    return window.$native.getEnv()
}

// 最終總的函數
const getCity = () => {
    const envMap = {
        app: getCityInAPP,
        browser: getCityInBrowser
    };
    const env = getEnv();
    
    return envMap[env]()
}

總結

  • 什么時候用策略模式?我認為是情況比較多的時候。比如:情況1要怎么怎么樣,情況2則要怎么怎么樣。簡單來說就是:不同的情況對應不同的方案。
  • 什么時候用適配器模式?我認為非常適合那種功能相同,字段不同的情況。就比如生活中,充電線的功能都是給手機充電🔋,但是iPhone的是lighting接口,一些安卓的是typeC接口,一些老式安卓是microUSB接口。大家的功能都是一樣的,只是一些小細節有所不同,這時候就可以通過一個適配器來兼容它們。簡單來說就是:消除細節差異


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM