iOS设计模式之桥接

6,525 阅读4分钟

定义

桥接是将抽象的部分与实现部分分离,使他们都可以独立的变化。目的是把抽象层次结构从其实现中分离出来,使其可以独立变更。抽象层定义了客户端使用的上层接口。实现层定义了供抽象层使用的底层接口。抽象类组合引用实现层对象,构成了桥接模式。

使用场景

适用桥接模式的场景如下:

  • 想要将抽象层与实现之间关系绑定延迟到运行时
  • 抽象与实现都需要通过子类化独立扩展
  • 对抽象的实现修改不影响客户端代码
  • 每个实现都需要额外的子类进行具体的细化
  • 想在多个抽象对象之间共享一个实现

类图结构

抽象类Abstaction持有Implementor对象,当客户端触发Abstraction的operation时,抽象类Abstraction会调用[imp operationImp]方法,传递给实现类Implementor。实现类可以根据现有的Implementor接口,扩展出更多的子类用于适配不同的场景。同样,抽象类Abstaction也可以根据不同的场景扩展对应的子类对象

代码实现

需求

根据需求,我们需要做一套游戏控制器,类似于游戏手柄的功能,用户可以使用这套游戏控制器在不同的操作系统(Mac,iPhone)玩经典游戏俄罗斯方块,贪吃蛇等。

分析

首先,我们分析出需要构建针对不同操作系统的控制器,UI不同,但是功能相同。

然后,需要构建针对不同游戏的操作命令。

我们假设游戏控制器的控制命令有up,down,left,rignt这些方向控制和action1,action2,action3等这些功能控制,那么,我们就可以根据需求构建我们的实现类图

类图

  1. 客户端Client与游戏控制器GameController进行交互,
  2. GameController持有GameEmulator对象,执行setCommand时,调用emulator的方法。
  3. GameIPhoneController继承GameController,实现iPhone端的触发,可以继续扩展Mac端的新子类
  4. GameBoxEmulator与GameSnakeEmulator都遵循GameEmulator协议,loadCommand是加载游戏指令,excuteCommand是执行指令操作。两个类分别针对俄罗斯方块,贪吃蛇游戏进行适配执行指令。后续可以增加新的子类以适配新的游戏。

代码实现

下面我们来看下具体的代码实现

@interface GameController : NSObject

@property (nonatomic, strong, readonly)id <GameEmulator> emulator;

- (instancetype)initWithEmulator:(id <GameEmulator>)emulator;

- (void)setCommand:(ControllerCommand)command;

//need subclass overwrite
- (void)up;
- (void)down;
- (void)left;
- (void)right;
- (void)action1;
- (void)action2;
- (void)action3;

@end

@implementation GameController

- (instancetype)initWithEmulator:(id <GameEmulator>)emulator {
    if (self = [super init]) {
        _emulator = emulator;
    }
    return self;
}

- (void)setCommand:(ControllerCommand)command {
    [self.emulator loadCommand:command];
    [self.emulator excuteCommand];
}

@end

GameController声明子类需要重写的方法,并且持有emulator对象,以及实现公共的方法setCommand,提供给子类调用传递指令。

@interface GameiPhoneController : GameController

- (void)up;
- (void)down;
- (void)left;
- (void)right;
- (void)action1;
- (void)action2;
- (void)action3;

@end
@implementation GameiPhoneController

- (void)up {
    [super setCommand:ControllerCommandUp];
}

- (void)down {
    [super setCommand:ControllerCommandDown];
}

- (void)left {
    [super setCommand:ControllerCommandLeft];
}

- (void)right {
    [super setCommand:ControllerCommandRight];
}

- (void)action1 {
    [super setCommand:ControllerCommandAction1];
}

- (void)action2 {
    [super setCommand:ControllerCommandAction2];
}

- (void)action3 {
    [super setCommand:ControllerCommandAction3];
}

@end

GameiPhoneController在.h文件中声明重写父类的方法,这是个好习惯,这样可以明确该子类覆写父类的那些方法。具体实现中,调用[super setCommand:]来传递指令给emulator

typedef NS_ENUM(NSUInteger, ControllerCommand) {
    ControllerCommandUp,
    ControllerCommandDown,
    ControllerCommandLeft,
    ControllerCommandRight,
    ControllerCommandAction1,
    ControllerCommandAction2,
    ControllerCommandAction3,
};

@protocol GameEmulator <NSObject>

- (void)loadCommand:(ControllerCommand)command;
- (void)excuteCommand;

@end

此处将GameEmulator声明为协议,更容易与具体子类进行解耦。同时ControllerCommand也定义的具体的指令类型。

@interface GameBoxEmulator : NSObject<GameEmulator>

- (void)loadCommand:(ControllerCommand)command;
- (void)excuteCommand;

@end
@implementation GameBoxEmulator


- (void)loadCommand:(ControllerCommand)command {
    //load in box
}
- (void)excuteCommand {
    //excute in box
}

@end

@interface GameSnakeEmulator : NSObject <GameEmulator>

- (void)loadCommand:(ControllerCommand)command;
- (void)excuteCommand;

@end

@implementation GameSnakeEmulator

- (void)loadCommand:(ControllerCommand)command {
    //load in snake
}
- (void)excuteCommand {
    //excute in snake
}

@end

GameBoxEmulator与GameSnakeEmulator都遵循GameEmulator协议,并且实现协议方法loadCommand与excuteCommand,针对俄罗斯方块和贪吃蛇分别做不同的指令适配。

总结

通过桥接模式,可以感受到对象组合的力量,GameEmulator如果单纯通过继承实现适配不同操作系统的不同游戏,代码将变得冗余且复杂,不易于维护和修改。 这也是我们优先使用对象组合而不是继承的原因之一。

桥接模式将一个接口适配到不同接口,将抽象从实现中分离出来,又以非常优雅的方式将他们联系在了一起。