设计模式笔记及Swift上的实现之一『ABSTRACT FACT
前言
最近开始在研读《设计模式》一书,书中主要是以 C++ 和 Smalltalk 为示例。所以我准备写一系列的读书笔记,并尝试将 23 种设计模式通过 Swift 实现,从而加深自己的理解。
设计模式介绍
意图
提供一个创建一系列相关或互相依赖对象的接口,而无需指定它们具体的类。
动机
存在多组功能相似的组件,但用户层(客户)不需要关心组件之间的差异,用户层只与抽象类定义的接口交互
适用性
- 一个系统要独立于它的产品的创建、组合和表示时。
- 一个系统要由多个产品系列中的一个来配置时。
- 当你要强调一系列相关的产品对象的设计以便进行联合使用时。
- 当你提供一个产品类库,而只想显示它们的接口而不是实现时。
结构
抽象工厂结构图参与者
- AbstractFactory
—— 声明一个创建抽象对象的操作接口。- ConcreteFactory
—— 实现创建具体产品对象的操作。- AbstractProduct
—— 为一类产品对象声明一个接口。- ConcreteProduct
—— 定义一个将被相应的具体工厂创建的产品对象。
—— 实现 AbstractProduct 接口。- Client
—— 仅使用由 AbstractFactory 和 AbstractProduct 类声明的接口。
协作
- 通常在运行时刻创建一个 ConcreteFactory 类的实例。
- AbstractFactory 将产品对象的创建延迟到它的的子类 ConcreteFactory
效果
优点
- 它分离了具体的类
- 使得交换产品系列变得容易
- 它有利于产品的一致性
缺点
- 难以支持新种类的产品
实现
- 将工厂作为单件
- 创建产品
- 定义可扩展的工厂
代码示例
Swift 版示例
书中以一个迷宫游戏为例子。
例子: 为电脑游戏创建一个迷宫。迷宫定义了一系列房间,一个房间知道他的邻居;可能的邻居要么是另一个房间,要么是一堵墙、或者是另一个房间的门。
创建一个迷宫工厂
首先创建个枚举表示四个(东南西北)方向
enum Direction {
case north, south, east, west
}
定义 MapSite
表示地图上的元素
protocol MapSite {
func enter()
}
定义 房间
、墙
、门
三种类型
protocol WallType: MapSite {
}
protocol DoorType: MapSite {
}
protocol WallType: MapSite {
}
这三种类型都属于地图上的元素,所以继承
MapSite
协议。
定义普通的房间
、墙
、门
struct Room: RoomType {
var sides: [MapSite?] = [nil, nil, nil, nil]
var roomNo: Int
init(no: Int) {
roomNo = no
}
}
struct Wall: WallType {
}
struct Door: DoorType {
var isOpen = false
var room1: RoomType
var room2: RoomType
init(r1: RoomType, r2: RoomType) {
room1 = r1
room2 = r2
}
mutating func otherSide(form room: RoomType) -> RoomType {
isOpen = true
if (room == room1) {
return room2
} else {
return room1
}
}
}
在 Swift 中我更喜欢使用
struct
代替class
,虽然struct
无法继承。但我可以使用protocol
实现部分继承的需求。
定义一个工厂,用于制造迷宫、房间、门、墙
protocol MazeFactory {
associatedtype RoomMazeType: RoomType
associatedtype WallMazeType: WallType
associatedtype DoorMazeType: DoorType
func makeMaze() -> Maze
func makeWall() -> WallMazeType
func makeRoom(_ n: Int) -> RoomMazeType
func makeDoor(r1: RoomMazeType, r2: RoomMazeType) -> DoorMazeType
}
extension MazeFactory {
func makeMaze() -> Maze {
return Maze()
}
func makeDoor(r1: Room, r2: Room) -> Door {
return Door(r1: r1, r2: r2)
}
func makeRoom(_ n: Int) -> Room {
return Room(no: n)
}
func makeWall() -> Wall {
return Wall()
}
}
工厂在书中 C++ 的例子中使用抽象类实现, Swift 没有抽象类,但 Swift 中可以通过 protocol
实现抽象类的功能。通过协议扩展提供默认的实现。
好了现在我们可以尝试通过我们的代码来生成一个普通的迷宫了。
struct MazeGame {
static func createMaze<T: MazeFactory>(mazeFactory: T) -> Maze {
var maze = mazeFactory.makeMaze()
var r1 = mazeFactory.makeRoom(1)
var r2 = mazeFactory.makeRoom(2)
let theDoor = mazeFactory.makeDoor(r1: r1, r2: r2)
r1.setSide(dect: .north, site: mazeFactory.makeWall())
r1.setSide(dect: .east, site: theDoor)
r1.setSide(dect: .south, site: mazeFactory.makeWall())
r1.setSide(dect: .west, site: mazeFactory.makeWall())
r2.setSide(dect: .north, site: mazeFactory.makeWall())
r2.setSide(dect: .east, site: mazeFactory.makeWall())
r2.setSide(dect: .south, site: mazeFactory.makeWall())
r2.setSide(dect: .west, site: theDoor)
maze.addRoom(room: r1)
maze.addRoom(room: r2)
return maze
}
}
// 使用工厂构建普通的迷宫
var normalMazeFactory = NormalMazeFactory()
var normalMaze = MazeGame.createMaze(mazeFactory: normalMazeFactory)
print(normalMaze)
打印结果:
===========================
Maze room:
room_2 Room
north is Optional(Wall)
south is Optional(Wall)
east is Optional(Wall)
west is Optional(Door)
room_1 Room
north is Optional(Wall)
south is Optional(Wall)
east is Optional(Door)
west is Optional(Wall)
===========================
嗯,感觉还不错。
创建一个魔法的迷宫
新的需求来了,产品经理告诉我们普通迷宫用户玩腻了,我们要新增一个魔法迷宫。魔法迷宫内有发光的房间
和需要咒语才能打开的门
。
首先定义发光的房间
和需要咒语才能打开的门
struct EnchantedRoom: RoomType {
......
}
struct DoorNeedingSpell: DoorType {
......
}
定义魔法迷宫工厂
struct EnchantedMazeFactory: MazeFactory {
typealias RoomMazeType = EnchantedRoom
typealias DoorMazeType = DoorNeedingSpell
func makeDoor(r1: EnchantedRoom, r2: EnchantedRoom) -> DoorNeedingSpell {
return DoorNeedingSpell(r1: r1, r2: r2)
}
func makeRoom(_ n: Int) -> RoomMazeType {
return EnchantedRoom(n, spell: Spell())
}
}
使用魔法迷宫工厂
var enchantedMazeFactory = EnchantedMazeFactory()
var enchantedMaze = MazeGame.createMaze(mazeFactory: enchantedMazeFactory)
print(enchantedMaze)
打印结果:
===========================
Maze room:
room_2 EnchantedRoom
north is Optional(Wall)
south is Optional(Wall)
east is Optional(Wall)
west is Optional(DoorNeedingSpell)
room_1 EnchantedRoom
north is Optional(Wall)
south is Optional(Wall)
east is Optional(DoorNeedingSpell)
west is Optional(Wall)
===========================
由于用户层只和 MazeFactory
的接口交互,所以完全不会感知到 EnchantedMazeFactory
和 NormalMazeFactory
的变化。
创建一个炸弹迷宫
又有新的需求!!要创建一个有炸弹的迷宫,我们需要一个有炸弹的房间,如果炸弹爆炸则会炸毁房间的墙。我们通过创建一个新的炸弹迷宫工厂,同样可以轻松的完成任务。Let to do.
定义一个有炸弹的房间
和会被炸毁的墙
struct RoomWithABomb: RoomType {
var sides: [MapSite?] = [nil, nil, nil, nil]
var roomNo: Int
var isBombe: Bool
init(_ n: Int, isBombe: Bool) {
roomNo = n
self.isBombe = isBombe
}
}
struct BombedWall: WallType {
var isBombed: Bool
init(_ isBombe: Bool) {
self.isBombed = isBombe
}
}
定义炸弹迷宫工厂
struct BombedMazeFactory: MazeFactory {
typealias WallMazeType = BombedWall
typealias RoomMazeType = RoomWithABomb
func makeWall() -> BombedWall {
return BombedWall(false)
}
func makeRoom(_ n: Int) -> RoomWithABomb {
return RoomWithABomb(n, isBombe: false)
}
func makeDoor(r1: RoomWithABomb, r2: RoomWithABomb) -> Door {
return Door(r1: r1, r2: r2)
}
}
使用工厂构建炸弹迷宫
var bombedMazeFactory = BombedMazeFactory()
var bombedMaze = MazeGame.createMaze(mazeFactory: bombedMazeFactory)
print(bombedMaze)
打印结果:
===========================
Maze room:
room_2 RoomWithABomb Bombe is false
north is Optional(BombedWall Bombe is false)
south is Optional(BombedWall Bombe is false)
east is Optional(BombedWall Bombe is false)
west is Optional(Door)
room_1 RoomWithABomb Bombe is false
north is Optional(BombedWall Bombe is false)
south is Optional(BombedWall Bombe is false)
east is Optional(Door)
west is Optional(BombedWall Bombe is false)
===========================
添加再多的迷宫,都不会对用户的代码造成影响。我们可以根据不同的配置,创建不同的工厂,但用户对此并无感知。
最后
抽象工厂模式适用于用户不需要知道具体的类型,只需要和协商好的接口交互。
欢迎讨论、批评、指错。