Project background

Involve business details, have done desensitization treatment.

In a mobile project, it is necessary to do the city location function. This city positioning at the beginning of nothing, just call autonavi city positioning API when the project initialization, nothing more.

After some business adjustments and product changes, the needs of urban positioning gradually became more complex. For example: embedded in a webview provided by a native APP, it is necessary to call the native method to get the city location of the outer layer of the APP; When thrown as a URL link to a third party, you want to lock down the city and do not automatically locate it.

After successfully fetching city, it will be stored in sessionStorage for global access.

And the trouble is, the positioning fields are different, like cityName, chineseCityName, and so on.

History code that is difficult to maintain

After several waves of development teams, this part of the code evolved into something like this:

async function getCity() {
    let city = {};
    
    // First fetch from sessionStorage
    city = JSON.parse(sessionStorage.getItem('city'))
    
    // The native method to get the city
    if(! city.cityCode &&window.$native.getCity) {
        // xxx
        return {
            cityName: nativeCity.cityName,
            cityCode: nativeCity.cityCode
        }
    }
    
    // If the URL has a bound city
    if ($route.query.cityCode) {
        // xxx
        return {
            cityName: matchCity.city,
            cityCode: matchCity.areaCode
        }
    }
    
    // Call autonavi API to locate
    if(! city.cityCode) {// xxx
        return {
            cityName: res.chineseCityName,
            cityCode: res.adcode
        }
    }
    
    // The single responsibility rule has been violated
    // The function name is getCity, but it does more than getCity can do
    if(! city.cityCode) { $router.replace('/select-city');
    } else {
        sessionStorage.setItem(city)
    }
    
    return city
}

Copy the code

This function is a simplified version of the function above, in fact, the function code is much longer than that, when the function with a little modification, test would have to return the whole environment: regression testing within the APP open positioning is normal, whether the regression test URL with the city orientation normal, normal regression test browser open position.

That was the reality of the project when I took over: when the positioning requirements changed, no one wanted to touch the stupid function. And I was actually assigned to pick up the pot.

Analysis before reconstruction

After some thinking, I initially decided to transform as follows:

  • Different environmentcontext(such as in the APP or in the browser), the corresponding get city policystrategyIt’s different. Here it isThe strategy pattern
  • To maintain the single-responsibility rule, a function only does one thing
  • For different field names, you can use the adapter pattern to achieve uniformity
  • The final output city format for global use should be consistent regardless of the environment

The modified code

It should be noted that many implementation details, such as try catch exception handling, some type judgments, etc. are omitted to make the structure clearer

/* * city field adapter * @params {Object} city various fields * @return {Object} standard unified format city Object */
const formatCityByAdapter = (city) = > {
    return {
        cityName: city.cityName || city.chineseCityName || city.city,
        cityCode: city.cityCode || city.adcode || city.areaCode
    }
}

// Get the city from Autonavi
const getCityFromAMap = async() = > {// xxx
  return city
}

// Get the city natively through the APP
const getCityFromNative = async() = > {// xxx
    return city
}

// Get the city through sessionStorage
const getCityFromSessionStorage = (a)= > {
    // xxx
    return city
}

// Get the city from the URL
const getCityFromUrl = (a)= > {
    // xxx
    return city
}

// The method of obtaining the city in the webview of APP. It is confirmed that there is no case of url fixed city
// If you are already in a native environment, there is no need to add conditions to determine whether there is a native method
const getCityInAPP = async() = > {const city = getCityFromSessionStorage() || await getCityFromNative()
    return formatCityByAdapter(res)
}

// The method of obtaining the city in the browser may have a fixed URL city
const getCityInBrowser = async() = > {const city = getCityFromSessionStorage() || getCityFromUrl() || await getCityFromAMap()
    return formatCityByAdapter(res)
}

// To obtain environment information, determine the environment first
const getEnv = (a)= > {
    return window.$native.getEnv()
}

// Final total function
const getCity = (a)= > {
    const envMap = {
        app: getCityInAPP,
        browser: getCityInBrowser
    };
    const env = getEnv();
    
    return envMap[env]()
}
Copy the code

conclusion

  • When to use strategic mode? I think it’s more of a situation. For example, case 1 is what it is, case 2 is what it is. To put it simply: Different situations correspond to different solutions.
  • When to use adapter mode? I think it’s perfect for situations where the function is the same but the fields are different. For example, in real life, the function of the charging cable is to charge the phone 🔋, but the iPhone has lighting port, some Android has typeC port, and some old Android has microUSB port. Everyone’s functions are the same, just a few small details are different, and you can use an adapter to accommodate them. In short: Eliminate the details.