設(shè)計(jì)模式筆記及Swift上的實(shí)現(xiàn)之一『ABSTRACT FACTORY(抽象工廠)』

前言

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

設(shè)計(jì)模式

介紹

意圖

提供一個(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)

抽象工廠結(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)

  1. 它分離了具體的類
  2. 使得交換產(chǎn)品系列變得容易
  3. 它有利于產(chǎn)品的一致性

缺點(diǎn)

  1. 難以支持新種類的產(chǎn)品

實(shí)現(xiàn)

  1. 將工廠作為單件
  2. 創(chuàng)建產(chǎn)品
  3. 定義可擴(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 的接口交互,所以完全不會感知到 EnchantedMazeFactoryNormalMazeFactory 的變化。

創(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é)商好的接口交互。

附:Playground 代碼

歡迎討論、批評、指錯(cuò)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容