Mastering Multi‑Platform Front‑End with the Strategy Pattern
This article walks through a real‑world multi‑app scenario, breaks down the core challenges, compares several implementation approaches—including naive if‑else, SDK abstraction, and the Strategy design pattern—and reflects on engineering trade‑offs to help front‑end engineers write cleaner, more maintainable code.
Introduction
The author, a newcomer at the Taobao ecosystem, describes how to tackle a complex requirement: deploying the "Recharge Center" across multiple third‑party apps such as Eleme, Taobao Lite, and UC Browser. The focus is on extracting core functionalities and applying design patterns to keep the codebase maintainable.
Scenario Overview
The key features that must work on every client are:
Address book access (different JSBridge APIs per client)
Payment integration (different JSBridge calls)
Account system bridging across varied internal accounts
Container compatibility (PHA container, H5, mini‑programs)
Client‑specific customizations (e.g., minimal flow for the Lite version)
Solution Approaches
1. Naïve if‑else per client
Directly check the client name and call the corresponding function.
// 业务代码文件 index.js
/**
* 获取通讯录列表
* @param clientName 端名称
*/
const getContactsList = (clientName) => {
if (clientName === 'eleme') {
getContactsListEleme()
} else if (clientName === 'taobao') {
getContactsListTaobao()
} else if (clientName === 'tianmao') {
getContactsListTianmao()
} else if (clientName === 'zhifubao') {
getContactsListZhifubao()
} else {
// 其他端
}
}Pros: Clear logic, quick to implement. Cons: Poor readability, code changes spread across business logic, testing all clients each time.
2. SDK abstraction with switch‑case
Encapsulate the client‑specific logic inside an SDK and expose a unified API.
/**
* 获取通讯录列表 sdk caontact.js
* @param clientName 端名称
* @param successCallback 成功回调
* @param failCallback 失败回调
*/
export default function (clientName, successCallback, failCallback) {
switch (clientName) {
case 'eleme':
getContactsListEleme()
break
case 'taobao':
getContactsListTaobao()
break
case 'zhifubao':
getContactsListTianmao()
break
case 'tianmao':
getContactsListZhifubao()
break
default:
// 省略
break
}
}
// 业务调用 index.js
<Contacts onIconClick={handleContactsClick} />
import getContactsList from 'Contacts'
import { clientName } from 'env'
const handleContactsClick = () => {
getContactsList(
clientName,
({ arr }) => {
this.setState({ contactsList: arr })
},
() => {
alert('获取通讯录失败')
}
)
}Pros: Clear module separation, unified business call, better readability. Cons: Still tightly coupled; each client change may require regression across all platforms.
3. Strategy Pattern
Introduce the Strategy design pattern to decouple strategy definition, creation, and usage.
Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.
/**
* 策略定义
*/
const strategies = {
eleme: () => { getContactsListEleme() },
taobao: () => { getContactsListTaobao() },
tianmao: () => { /* 省略 */ }
}
/**
* 策略创建
*/
const getContactsStrategy = (clientName) => {
if (!clientName) {
throw new Error('clientName is empty.')
}
return strategies[clientName]
}
/**
* 策略使用
*/
import { clientName } from 'env'
getContactsStrategy(clientName)()The pattern follows the Dependency Inversion Principle, achieving high cohesion and loose coupling. Adding a new client only requires updating the strategy map, minimizing code changes.
4. Modular per‑client strategy files
Separate each client’s strategies into dedicated modules, enabling potential tree‑shaking during build.
/** * 饿了么端策略定义module */
export const elmcStrategies = {
contacts: () => { getContactsListEleme() },
pay: () => { payEleme() }
}
/** * 手淘端策略定义module */
export const tbStrategies = {
contacts: () => { getContactsListTaobao() },
pay: () => { payTaobao() }
}
/** * 策略创建 index.js */
import tbStrategies from './tbStrategies'
import elmcStrategies from './elmcStrategies'
export const getClientStrategy = (clientName) => {
const strategies = { elmc: elmcStrategies, tb: tbStrategies }
if (!clientName) {
throw new Error('clientName is empty.')
}
return strategies[clientName]
}
/** * 策略使用 pay */
import { clientName } from 'env'
getClientStrategy(clientName).pay()Directory illustration:
5. Forward‑looking idea
For scenarios where the recharge feature must be embedded in third‑party apps, the author suggests a serverless approach: expose a Faas function that dynamically selects the optimal wake‑up strategy, reducing code churn and improving activation rates.
Reflection & Takeaways
The article emphasizes that while the Strategy pattern solves the immediate problem of tangled if‑else logic, engineers should also consider build‑time optimizations, performance, and long‑term maintainability. Understanding design principles such as the Open/Closed Principle and the Dependency Inversion Principle is more valuable than blindly applying a pattern.
Conclusion
The recommended workflow is: receive a complex requirement → clarify scope → decompose technical challenges → implement → refactor using design patterns → iterate and document. By abstracting multi‑client logic, front‑end engineers can deliver features faster, keep codebases stable, and accelerate personal growth.
Taobao Frontend Technology
The frontend landscape is constantly evolving, with rapid innovations across familiar languages. Like us, your understanding of the frontend is continually refreshed. Join us on Taobao, a vibrant, all‑encompassing platform, to uncover limitless potential.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
