如何在Objective-C中模拟双分派(Double Dispatch)
当然可以实现!虽然Objective-C本身没有原生的方法重载和编译期双分派支持,但我们可以利用它的动态消息发送机制和运行时类型识别特性来模拟双分派,下面给你几个实用的思路和代码示例:
思路1:基于类型判断的直接实现
这是最直观的方式,通过两次方法调用来完成双分派:第一次根据发送者的类型进入对应方法,第二次在方法内部判断接收者的类型,调用具体的处理逻辑。
举个碰撞检测的经典例子:
// 基类Shape @interface Shape : NSObject - (void)collideWithShape:(Shape *)shape; @end @implementation Shape - (void)collideWithShape:(Shape *)shape { // 默认处理,或抛出异常提示未实现 NSLog(@"Unsupported collision type"); } @end // Circle子类 @interface Circle : Shape @end @implementation Circle - (void)collideWithShape:(Shape *)shape { if ([shape isKindOfClass:[Circle class]]) { [self collideWithCircle:(Circle *)shape]; } else if ([shape isKindOfClass:[Rectangle class]]) { [self collideWithRectangle:(Rectangle *)shape]; } } - (void)collideWithCircle:(Circle *)circle { NSLog(@"Circle collides with Circle: 处理圆形间的碰撞逻辑"); } - (void)collideWithRectangle:(Rectangle *)rect { NSLog(@"Circle collides with Rectangle: 处理圆形与矩形的碰撞逻辑"); } @end // Rectangle子类 @interface Rectangle : Shape @end @implementation Rectangle - (void)collideWithShape:(Shape *)shape { if ([shape isKindOfClass:[Circle class]]) { [self collideWithCircle:(Circle *)shape]; } else if ([shape isKindOfClass:[Rectangle class]]) { [self collideWithRectangle:(Rectangle *)shape]; } } - (void)collideWithCircle:(Circle *)circle { NSLog(@"Rectangle collides with Circle: 处理矩形与圆形的碰撞逻辑"); } - (void)collideWithRectangle:(Rectangle *)rect { NSLog(@"Rectangle collides with Rectangle: 处理矩形间的碰撞逻辑"); } @end
使用方式:
Shape *circle1 = [[Circle alloc] init]; Shape *rect = [[Rectangle alloc] init]; [circle1 collideWithShape:rect]; // 输出 "Circle collides with Rectangle: 处理圆形与矩形的碰撞逻辑"
优缺点:实现简单,容易理解;但新增Shape子类时,需要修改所有现有子类的collideWithShape:方法,违反开闭原则,适合小型场景。
思路2:利用动态消息发送优化
Objective-C的运行时允许我们动态生成选择器(SEL)并发送消息,这样可以避免大量的if-else判断,让代码更简洁。
修改Shape基类的实现:
@implementation Shape - (void)collideWithShape:(Shape *)shape { // 根据接收者的类名动态生成选择器 NSString *selectorName = [NSString stringWithFormat:@"collideWith%@:", NSStringFromClass([shape class])]; SEL targetSelector = NSSelectorFromString(selectorName); // 检查当前对象是否响应该选择器,避免崩溃 if ([self respondsToSelector:targetSelector]) { // 强制类型转换后发送消息(需要导入<objc/message.h>) ((void (*)(id, SEL, id))objc_msgSend)(self, targetSelector, shape); } else { NSLog(@"Unsupported collision type"); } } @end
此时Circle和Rectangle只需要保留具体的碰撞方法(比如collideWithCircle:、collideWithRectangle:),不需要再写if-else判断逻辑。
优缺点:减少了冗余的条件判断,代码更简洁;但新增子类时,仍需要在其他类中添加对应的处理方法,不过相比思路1,修改量更小。
思路3:访问者模式(推荐用于复杂场景)
访问者模式是双分派的经典应用,它通过分离数据结构和操作逻辑,完美实现双分派,同时符合开闭原则,适合复杂的业务场景。
步骤1:定义访问者协议
@protocol ShapeVisitor <NSObject> - (void)visitCircle:(Circle *)circle; - (void)visitRectangle:(Rectangle *)rectangle; @end
步骤2:修改Shape基类,添加接受访问者的方法
@interface Shape : NSObject - (void)acceptVisitor:(id<ShapeVisitor>)visitor; @end @implementation Shape - (void)acceptVisitor:(id<ShapeVisitor>)visitor { NSLog(@"Unsupported shape type for visitor"); } @end
步骤3:实现子类的acceptVisitor方法
@implementation Circle - (void)acceptVisitor:(id<ShapeVisitor>)visitor { // 第一次分派:根据自身类型调用访问者对应的方法 [visitor visitCircle:self]; } @end @implementation Rectangle - (void)acceptVisitor:(id<ShapeVisitor>)visitor { [visitor visitRectangle:self]; } @end
步骤4:实现具体的访问者(比如碰撞处理访问者)
@interface CollisionVisitor : NSObject <ShapeVisitor> // 存储另一个参与碰撞的形状 @property (nonatomic, strong) Shape *otherShape; @end @implementation CollisionVisitor - (void)visitCircle:(Circle *)circle { // 第二次分派:根据otherShape的类型执行具体逻辑 if ([_otherShape isKindOfClass:[Circle class]]) { NSLog(@"Circle collides with Circle: 处理圆形间的碰撞逻辑"); } else if ([_otherShape isKindOfClass:[Rectangle class]]) { // 让另一个形状接受当前访问者,完成双向处理 [_otherShape acceptVisitor:self]; } } - (void)visitRectangle:(Rectangle *)rectangle { if ([_otherShape isKindOfClass:[Circle class]]) { NSLog(@"Rectangle collides with Circle: 处理矩形与圆形的碰撞逻辑"); } else if ([_otherShape isKindOfClass:[Rectangle class]]) { NSLog(@"Rectangle collides with Rectangle: 处理矩形间的碰撞逻辑"); } } @end
使用方式
Circle *circle = [[Circle alloc] init]; Rectangle *rect = [[Rectangle alloc] init]; CollisionVisitor *visitor = [[CollisionVisitor alloc] init]; visitor.otherShape = rect; [circle acceptVisitor:visitor]; // 输出 "Rectangle collides with Circle: 处理矩形与圆形的碰撞逻辑"
优缺点:完全符合开闭原则,新增Shape子类时,只需要修改访问者协议和添加对应的visit方法,不需要修改现有Shape类的代码;结构清晰,适合复杂场景,但实现相对复杂一些。
内容的提问来源于stack exchange,提问作者Josué H.
相关产品推荐
相关产品推荐

