设计模式笔记及Swift上的实现之一『ABSTRACT FACTORY(抽象工厂)』

前言

最近开始在研读《设计模式》一书,书中主要是以 C++ 和 Smalltalk 为示例。所以我准备写一系列的读书笔记,并尝试将 23 种设计模式通过 Swift 实现,从而加深自己的理解。

设计模式

介绍

意图

提供一个创建一系列相关或互相依赖对象的接口,而无需指定它们具体的类。

动机

存在多组功能相似的组件,但用户层(客户)不需要关心组件之间的差异,用户层只与抽象类定义的接口交互

适用性

  • 一个系统要独立于它的产品的创建、组合和表示时。
  • 一个系统要由多个产品系列中的一个来配置时。
  • 当你要强调一系列相关的产品对象的设计以便进行联合使用时。
  • 当你提供一个产品类库,而只想显示它们的接口而不是实现时。

结构

抽象工厂结构图

参与者

  • AbstractFactory

    —— 声明一个创建抽象对象的操作接口。
  • ConcreteFactory

    —— 实现创建具体产品对象的操作。
  • AbstractProduct

    —— 为一类产品对象声明一个接口。
  • ConcreteProduct

    —— 定义一个将被相应的具体工厂创建的产品对象。

    —— 实现 AbstractProduct 接口。
  • Client

    —— 仅使用由 AbstractFactory 和 AbstractProduct 类声明的接口。

协作

  • 通常在运行时刻创建一个 ConcreteFactory 类的实例。
  • AbstractFactory 将产品对象的创建延迟到它的的子类 ConcreteFactory

效果

优点

  1. 它分离了具体的类
  2. 使得交换产品系列变得容易
  3. 它有利于产品的一致性

缺点

  1. 难以支持新种类的产品

实现

  1. 将工厂作为单件
  2. 创建产品
  3. 定义可扩展的工厂

代码示例

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 的接口交互,所以完全不会感知到 EnchantedMazeFactoryNormalMazeFactory 的变化。

创建一个炸弹迷宫

又有新的需求!!要创建一个有炸弹的迷宫,我们需要一个有炸弹的房间,如果炸弹爆炸则会炸毁房间的墙。我们通过创建一个新的炸弹迷宫工厂,同样可以轻松的完成任务。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)
===========================

添加再多的迷宫,都不会对用户的代码造成影响。我们可以根据不同的配置,创建不同的工厂,但用户对此并无感知。

最后

抽象工厂模式适用于用户不需要知道具体的类型,只需要和协商好的接口交互。

附:Playground 代码

欢迎讨论、批评、指错。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341

推荐阅读更多精彩内容