设计模式示例
先写出最直观的烂代码,然后一步步因为需求变更而“被迫”演进成设计模式。
演进案例一:电商收银台的计费引擎(策略模式)
语言:Swift
场景:计算用户的最终支付金额
需求 1:所有用户全额支付,无任何折扣
First Attempt: Raw Implementation
class PriceCalculator {
func calculate(price: Double, userType: String) -> Double {
return price
}
}
代码很简单,生活很美好。直到运营提出了新需求。
需求 2:VIP 用户打 8 折,普通用户原价
Second Attempt: Using If-Else
class PriceCalculator {
func calculate(price: Double, userType: String) -> Double {
if userType == "VIP" {
return price * 0.8
}
return price
}
}
暂时还能忍受。
需求 3:新增“新用户立减20元”,新增“年底大促满100减50”
Third Attempt: Switch / If-Else Hell
class PriceCalculator {
func calculate(price: Double, userType: String) -> Double {
if userType == "VIP" {
return price * 0.8
} else if userType == "NewUser" {
return price - 20.0
} else if userType == "YearEndPromo" {
return price >= 100 ? price - 50 : price
}
return price
}
}
坏味道:
- 违反开闭原则:每次运营搞活动,你都要修改这个核心类,测试要重新测一遍,风险极大。
- 逻辑臃肿:如果有 50 种活动,这个方法会有几千行。
需求 4:活动规则复杂多变,甚至需要动态下发
Fourth Attempt: Protocol (Strategy Pattern)
我们将“怎么算钱”这件事抽象成一个协议(接口)。
// 1. 定义策略协议
protocol DiscountStrategy {
func applyDiscount(price: Double) -> Double
}
// 2. 具体的策略实现
struct VipDiscount: DiscountStrategy {
func applyDiscount(price: Double) -> Double { return price * 0.8 }
}
struct NewUserDiscount: DiscountStrategy {
func applyDiscount(price: Double) -> Double { return price - 20 }
}
struct NormalDiscount: DiscountStrategy {
func applyDiscount(price: Double) -> Double { return price }
}
// 3. 上下文 (Context)
class PriceCalculator {
// 注入策略
func calculate(price: Double, strategy: DiscountStrategy) -> Double {
return strategy.applyDiscount(price: price)
}
}
// 使用
let calculator = PriceCalculator()
let finalPrice = calculator.calculate(price: 100, strategy: VipDiscount())
现在,新增活动只需要新建一个 struct,无需修改 Calculator。
优化:Swift 的函数式演进 (Functional Approach)
在 Swift 中,如果一个协议只有一个方法,我们甚至不需要创建那么多 struct/class。函数本身就是一种策略。
Fifth Attempt: Strategies as Closures
// 定义一个闭包类型:输入原价,返回折后价
typealias DiscountStrategy = (Double) -> Double
class PriceCalculator {
func calculate(price: Double, strategy: DiscountStrategy) -> Double {
return strategy(price)
}
}
// 预定义的策略库
struct Strategies {
static let vip: DiscountStrategy = { $0 * 0.8 }
static let newUser: DiscountStrategy = { $0 - 20 }
static let normal: DiscountStrategy = { $0 }
}
// 使用
let calc = PriceCalculator()
// 方式 A: 使用预定义策略
calc.calculate(price: 100, strategy: Strategies.vip)
// 方式 B: 现场临时定义一个“打9折”的策略 (极度灵活)
calc.calculate(price: 100, strategy: { price in
return price * 0.9
})
最终形态:利用 Swift 的闭包特性,策略模式变成了简单的函数传递。既保留了扩展性,又消除了大量的样板代码。
演进案例二:登录表单校验(责任链模式)
语言:Objective-C
场景:用户点击登录,需要依次校验:账号非空 -> 格式正确 -> 密码长度 -> 是否包含非法字符。
需求 1:简单的账号密码非空检查
First Attempt: Hard Code in ViewController
- (void)loginWithEmail:(NSString *)email password:(NSString *)password {
if (email.length == 0) {
NSLog(@"邮箱不能为空");
return;
}
if (password.length == 0) {
NSLog(@"密码不能为空");
return;
}
[self doLoginRequest];
}
需求 2:增加格式校验、最小长度校验、黑名单校验
Second Attempt: The Massive Method
- (void)loginWithEmail:(NSString *)email password:(NSString *)password {
if (email.length == 0) {
NSLog(@"邮箱不能为空");
return;
}
if (![email containsString:@"@"]) {
NSLog(@"邮箱格式错误");
return;
}
if (password.length < 6) {
NSLog(@"密码太短");
return;
}
if ([email containsString:@"admin"]) { // 模拟黑名单
NSLog(@"禁止使用此账号");
return;
}
[self doLoginRequest];
}
坏味道:
- 这个方法像面条一样越来越长。
- 如果我想先校验“黑名单”,再校验“格式”,需要手动调整代码顺序,容易出错。
- 很难复用(比如“注册”页面也需要同样的邮箱校验逻辑)。
需求 3:校验逻辑可复用,且支持动态配置校验顺序
Third Attempt: Chain of Responsibility (Classic OO)
我们需要把每一个校验逻辑拆分成独立的“Handler”。
抽象基类:
@class LoginContext; // 封装参数的对象
@interface Validator : NSObject
@property (nonatomic, strong) Validator *nextValidator;
- (BOOL)validate:(LoginContext *)context;
@end
@implementation Validator
- (BOOL)validate:(LoginContext *)context {
// 默认行为:如果自己没处理,就甩给下一个;如果没有下一个,说明全通过
if (self.nextValidator) {
return [self.nextValidator validate:context];
}
return YES;
}
@end
具体实现:
// 邮箱非空校验
@interface EmailEmptyValidator : Validator
@end
@implementation EmailEmptyValidator
- (BOOL)validate:(LoginContext *)context {
if (context.email.length == 0) {
NSLog(@"邮箱为空");
return NO; // 校验失败,链条终止
}
return [super validate:context]; // 传递给下一个
}
@end
// 密码长度校验
@interface PasswordLenValidator : Validator
@end
@implementation PasswordLenValidator
- (BOOL)validate:(LoginContext *)context {
if (context.password.length < 6) {
NSLog(@"密码太短");
return NO;
}
return [super validate:context];
}
@end
组装链条:
Validator *v1 = [EmailEmptyValidator new];
Validator *v2 = [PasswordLenValidator new];
v1.nextValidator = v2; // 链起来
[v1 validate:context];
优化:消除手动链表的繁琐 (Manager / Array Approach)
在 OC 中,手动写 v1.next = v2; v2.next = v3 既罗嗦又容易搞错指针。我们可以做一个“管理器”来遍历数组,这在逻辑上等同于责任链。
Fourth Attempt: Validation Manager
@interface ValidationManager : NSObject
- (void)addValidator:(Validator *)validator;
- (BOOL)runValidation:(LoginContext *)context;
@end
@implementation ValidationManager {
NSMutableArray *_validators;
}
- (instancetype)init {
if (self = [super init]) { _validators = [NSMutableArray array]; }
return self;
}
- (void)addValidator:(Validator *)validator {
[_validators addObject:validator];
}
- (BOOL)runValidation:(LoginContext *)context {
for (Validator *v in _validators) {
// 这里不再调用 next,而是由 Manager 统一调度
// 注意:Validator 类里的 [super validate] 逻辑需要去掉,改为只负责自己的逻辑
if (![v check:context]) {
return NO;
}
}
return YES;
}
@end
// 使用
ValidationManager *mgr = [ValidationManager new];
[mgr addValidator:[EmailEmptyValidator new]];
[mgr addValidator:[PasswordLenValidator new]];
// 随意调整顺序
// [mgr addValidator:[BlacklistValidator new]];
if ([mgr runValidation:context]) {
[self doLogin];
}
总结:从 iOS 开发视角看设计模式
-
策略模式 (Strategy)
- iOS 常见场景:
UITableView的delegate其实就是策略模式的一种变体(把“怎么渲染cell”的策略交给代理去实现)。还有排序算法、图片缓存策略(LRU/FIFO)。 - 演进总结:
Hard Code->Enum->Protocol->Closure。
- iOS 常见场景:
-
责任链模式 (Chain of Responsibility)
- iOS 常见场景:
UIResponder的事件传递链(点击 View,没处理就传给 SuperView,直到 ViewController)。 - 演进总结:
Massive Method->LinkedList Class->Array Pipeline。
- iOS 常见场景:
最终建议:
在 Swift/OC 开发中,不要过度封装。就像《精益编程》里说的,如果一个简单的 Block 或者 Switch 能清晰解决问题,就不要搞出 10 个类来实现一个“模式”。模式是用来解决痛点的,不是用来展示技术的。
未经作者授权,禁止转载
本文来自博客园,作者:CoderWGB,转载请注明原文链接:https://www.cnblogs.com/wgb1234/articles/19514204
THE END

浙公网安备 33010602011771号