iOS 开发中的渐进式设计:先简单实现,再优雅扩展
2025-08-26 本文已影响0人
黄花菜先生
架构师的价值不在于构建永恒完美的系统,而在于让系统能以最小成本持续演进。
一、在「写死」与「过度设计」之间求平衡
iOS 项目开发里,我们常常走向两个极端:
1. 过度设计 (Over-engineering)
-
表现:一上来就写一堆
@protocol、抽象基类,预测未来可能的各种需求。 - 问题:违背 YAGNI (You Aren’t Gonna Need It),耗时耗力,复杂度反而更高。
- OC 常见场景:协议只有一个实现,继承层次不必要地过深。
2. 毫无设计 (No design)
-
表现:所有逻辑都堆在
UIViewController里,方法动辄几百行。 - 问题:需求一变,改动一处牵一发而动全身,重构风险极高。
真正的挑战:如何找到一个中间点,既能快速交付,又能低成本应对变化?
二、渐进式重构:像搭乐高一样写代码
渐进式设计的核心:
先用简单可用的“积木”完成需求,等真正遇到变化时,再替换或增强积木,而不是一开始就造复杂的系统。
阶段一:简单实现,清晰隔离
初始版本只做当前需求,同时通过 Delegate/Block 预留扩展点。
// ShakeView.h
@interface ShakeView : UIView
@property (nonatomic, copy) void (^onAnimationStart)(void);
- (void)startShakeAnimation;
@end
// ShakeView.m
@implementation ShakeView
- (void)startShakeAnimation {
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position.x"];
animation.duration = 0.3;
animation.autoreverses = YES;
animation.fromValue = @(self.center.x - 10);
animation.toValue = @(self.center.x + 10);
[self.layer addAnimation:animation forKey:@"shake"];
if (self.onAnimationStart) {
self.onAnimationStart();
}
}
@end
要点:
- 快速完成核心逻辑(动画)。
- 通过 Block/Delegate 低成本预留扩展(声音、埋点等)。
- 保持方法单一,命名清晰,方便后续拆分。
阶段二:需求驱动,抽离协议
当出现以下情况时,就该考虑抽象:
- 同一功能出现第二、第三种实现。
- 产品 roadmap 明确未来会有多种策略。
- 测试发现 if-else 分支过多,难以覆盖。
重构为策略模式:
// 协议定义
@protocol ShakeViewAnimator <NSObject>
- (void)addAnimationToView:(UIView *)view;
@end
// 默认动画器
@interface BasicShakeAnimator : NSObject <ShakeViewAnimator>
@end
@implementation BasicShakeAnimator
- (void)addAnimationToView:(UIView *)view {
// 简单晃动
}
@end
// 高级动画器
@interface PremiumBounceAnimator : NSObject <ShakeViewAnimator>
@end
@implementation PremiumBounceAnimator
- (void)addAnimationToView:(UIView *)view {
// 更复杂的弹跳
}
@end
// ShakeView 支持注入策略
@interface ShakeView : UIView
@property (nonatomic, strong) id<ShakeViewAnimator> animator;
- (void)startShakeAnimation;
@end
@implementation ShakeView
- (void)startShakeAnimation {
[self.animator addAnimationToView:self];
if (self.onAnimationStart) {
self.onAnimationStart();
}
}
@end
使用方式:
ShakeView *shakeView = [[ShakeView alloc] initWithFrame:frame];
shakeView.animator = [PremiumBounceAnimator new];
[shakeView startShakeAnimation];
好处:
- 避免继承膨胀 (
BasicShakeView,PremiumShakeView…)。 - 新增动画方式只需实现一个 Animator,不动 ShakeView 本身。
- 遵循 OCP(开放封闭原则),演进成本低。
阶段三:SOLID 原则的自然涌现
在代码渐进到这一阶段时,你会发现:
- SRP:ShakeView 只负责协调,动画交给 Animator。
- OCP:新增功能时无需修改 ShakeView。
- DIP:ShakeView 依赖抽象协议,而非具体类。
- LSP:任何 Animator 都能替换使用。
- ISP:协议单一、专注。
原则不是硬套,而是代码演进的自然结果。
三、总结与实践建议
- 始于简单:相信 YAGNI,首版代码够用即可。
- 预见变化:只提前抽象最可能且代价最高的点。
- 三次法则:变化出现第三次,就该抽象。
- 发挥 OC 动态性:用 Category、消息转发等做低侵入式扩展。
最终目标是:
当下足够简单,未来足够灵活;当前成本最低,演进成本可控。