前言
最近開始在研讀《設(shè)計(jì)模式》一書,書中主要是以 C++ 和 Smalltalk 為示例。所以我準(zhǔn)備寫一系列的讀書筆記,并嘗試將 23 種設(shè)計(jì)模式通過 Swift 實(shí)現(xiàn),從而加深自己的理解。

介紹
意圖
提供一個(gè)創(chuàng)建一系列相關(guān)或互相依賴對象的接口,而無需指定它們具體的類。
動(dòng)機(jī)
存在多組功能相似的組件,但用戶層(客戶)不需要關(guān)心組件之間的差異,用戶層只與抽象類定義的接口交互
適用性
- 一個(gè)系統(tǒng)要獨(dú)立于它的產(chǎn)品的創(chuàng)建、組合和表示時(shí)。
- 一個(gè)系統(tǒng)要由多個(gè)產(chǎn)品系列中的一個(gè)來配置時(shí)。
- 當(dāng)你要強(qiáng)調(diào)一系列相關(guān)的產(chǎn)品對象的設(shè)計(jì)以便進(jìn)行聯(lián)合使用時(shí)。
- 當(dāng)你提供一個(gè)產(chǎn)品類庫,而只想顯示它們的接口而不是實(shí)現(xiàn)時(shí)。
結(jié)構(gòu)

參與者
- AbstractFactory
—— 聲明一個(gè)創(chuàng)建抽象對象的操作接口。- ConcreteFactory
—— 實(shí)現(xiàn)創(chuàng)建具體產(chǎn)品對象的操作。- AbstractProduct
—— 為一類產(chǎn)品對象聲明一個(gè)接口。- ConcreteProduct
—— 定義一個(gè)將被相應(yīng)的具體工廠創(chuàng)建的產(chǎn)品對象。
—— 實(shí)現(xiàn) AbstractProduct 接口。- Client
—— 僅使用由 AbstractFactory 和 AbstractProduct 類聲明的接口。
協(xié)作
- 通常在運(yùn)行時(shí)刻創(chuàng)建一個(gè) ConcreteFactory 類的實(shí)例。
- AbstractFactory 將產(chǎn)品對象的創(chuàng)建延遲到它的的子類 ConcreteFactory
效果
優(yōu)點(diǎn)
- 它分離了具體的類
- 使得交換產(chǎn)品系列變得容易
- 它有利于產(chǎn)品的一致性
缺點(diǎn)
- 難以支持新種類的產(chǎn)品
實(shí)現(xiàn)
- 將工廠作為單件
- 創(chuàng)建產(chǎn)品
- 定義可擴(kuò)展的工廠
代碼示例
Swift 版示例
書中以一個(gè)迷宮游戲?yàn)槔印?/p>
例子: 為電腦游戲創(chuàng)建一個(gè)迷宮。迷宮定義了一系列房間,一個(gè)房間知道他的鄰居;可能的鄰居要么是另一個(gè)房間,要么是一堵墻、或者是另一個(gè)房間的門。
創(chuàng)建一個(gè)迷宮工廠
首先創(chuàng)建個(gè)枚舉表示四個(gè)(東南西北)方向
enum Direction {
case north, south, east, west
}
定義 MapSite 表示地圖上的元素
protocol MapSite {
func enter()
}
定義 房間、墻、門 三種類型
protocol WallType: MapSite {
}
protocol DoorType: MapSite {
}
protocol WallType: MapSite {
}
這三種類型都屬于地圖上的元素,所以繼承
MapSite協(xié)議。
定義普通的房間、墻、門
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實(shí)現(xiàn)部分繼承的需求。
定義一個(gè)工廠,用于制造迷宮、房間、門、墻
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++ 的例子中使用抽象類實(shí)現(xiàn), Swift 沒有抽象類,但 Swift 中可以通過 protocol 實(shí)現(xiàn)抽象類的功能。通過協(xié)議擴(kuò)展提供默認(rèn)的實(shí)現(xiàn)。
好了現(xiàn)在我們可以嘗試通過我們的代碼來生成一個(gè)普通的迷宮了。
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
}
}
// 使用工廠構(gòu)建普通的迷宮
var normalMazeFactory = NormalMazeFactory()
var normalMaze = MazeGame.createMaze(mazeFactory: normalMazeFactory)
print(normalMaze)
打印結(jié)果:
===========================
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)
===========================
嗯,感覺還不錯(cuò)。
創(chuàng)建一個(gè)魔法的迷宮
新的需求來了,產(chǎn)品經(jīng)理告訴我們普通迷宮用戶玩膩了,我們要新增一個(gè)魔法迷宮。魔法迷宮內(nèi)有發(fā)光的房間和需要咒語才能打開的門。
首先定義發(fā)光的房間和需要咒語才能打開的門
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)
打印結(jié)果:
===========================
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 的變化。
創(chuàng)建一個(gè)炸彈迷宮
又有新的需求??!要?jiǎng)?chuàng)建一個(gè)有炸彈的迷宮,我們需要一個(gè)有炸彈的房間,如果炸彈爆炸則會炸毀房間的墻。我們通過創(chuàng)建一個(gè)新的炸彈迷宮工廠,同樣可以輕松的完成任務(wù)。Let to do.
定義一個(gè)有炸彈的房間和會被炸毀的墻
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)
}
}
使用工廠構(gòu)建炸彈迷宮
var bombedMazeFactory = BombedMazeFactory()
var bombedMaze = MazeGame.createMaze(mazeFactory: bombedMazeFactory)
print(bombedMaze)
打印結(jié)果:
===========================
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)
===========================
添加再多的迷宮,都不會對用戶的代碼造成影響。我們可以根據(jù)不同的配置,創(chuàng)建不同的工廠,但用戶對此并無感知。
最后
抽象工廠模式適用于用戶不需要知道具體的類型,只需要和協(xié)商好的接口交互。
歡迎討論、批評、指錯(cuò)。