服务端下发动态Action时如何避免switch不违反开闭原则OCP
实现方案
核心思路是用字符串到动作构造器的映射表代替硬编码的switch判断,新增动作时只需要新增对应实现类并注册到映射表即可,不需要修改原有解析逻辑,完全符合开闭原则。
第一步:扩展Actionable协议,支持从服务端下发的Action结构体初始化
因为每个动作需要拿到服务端下发的额外参数(比如跳转页面需要传的ID、弹窗需要的文案等),所以先给协议加初始化要求:
protocol Actionable { init?(rawAction: Action) func execute() }
第二步:实现各业务动作类
每个动作单独实现Actionable协议,和原有逻辑一致:
// 跳转到ScreenA的动作实现 struct GoToScreenAAction: Actionable { private let productId: String init?(rawAction: Action) { // 从rawAction里解析当前动作需要的参数,解析失败返回nil guard let productId = rawAction.extraParams?["productId"] as? String else { return nil } self.productId = productId } func execute() { // 原有跳转ScreenA的逻辑,携带productId参数 let screenA = ScreenAViewController(productId: productId) currentNavigationController?.pushViewController(screenA, animated: true) } } // 弹窗动作实现 struct PresentAlertAction: Actionable { private let title: String private let content: String init?(rawAction: Action) { guard let title = rawAction.extraParams?["title"] as? String, let content = rawAction.extraParams?["content"] as? String else { return nil } self.title = title self.content = content } func execute() { let alert = UIAlertController(title: title, message: content, preferredStyle: .alert) alert.addAction(UIAlertAction(title: "确定", style: .default)) currentViewController?.present(alert, animated: true) } }
第三步:实现动作注册表,替代switch判断
用一个全局字典维护动作类型字符串和对应Actionable实现类的映射关系,新增动作只需要注册新的映射即可:
class ActionRegistry { static let shared = ActionRegistry() private var actionTypeMap: [String: Actionable.Type] = [:] private init() { // 初始化时注册所有支持的动作 registerAction(type: "goToScreenA", actionClass: GoToScreenAAction.self) registerAction(type: "goToScreenB", actionClass: GoToScreenBAction.self) registerAction(type: "presentAlert", actionClass: PresentAlertAction.self) } /// 注册动作类型和对应实现类 func registerAction(type: String, actionClass: Actionable.Type) { actionTypeMap[type] = actionClass } /// 把服务端下发的原始Action转为可执行的Actionable实例 func resolveAction(rawAction: Action) -> Actionable? { guard let actionClass = actionTypeMap[rawAction.type] else { return nil } return actionClass.init(rawAction: rawAction) } }
第四步:改造ActionResolver,支持直接处理服务端下发的原始Action
class ActionResolver { static func execute(rawAction: Action) { guard let actionable = ActionRegistry.shared.resolveAction(rawAction: rawAction) else { // 不存在的动作类型直接忽略,可加日志埋点排查问题 debugPrint("unsupported action type: \(rawAction.type)") return } actionable.execute() } }
最终调用方式
用户点击触发动作时,直接传入服务端下发的原始Action即可:
func onButtonClick(rawAction: Action) { ActionResolver.execute(rawAction: rawAction) }
新增动作的流程
后续新增动作完全不需要修改原有解析逻辑,只需要两步:
- 新增一个实现Actionable协议的结构体,写对应业务逻辑
- 在ActionRegistry的初始化方法里新增一行注册代码
该方案完全符合开闭原则,没有硬编码的switch/if判断,所有动作逻辑内聚在各自的实现类里,便于维护和单元测试。
内容的提问来源于stack exchange,提问作者Hoven
相关产品推荐
相关产品推荐

