美文网首页程序员首页投稿(暂停使用,暂停投稿)
设计模式笔记及Swift上的实现之一『ABSTRACT FACT

设计模式笔记及Swift上的实现之一『ABSTRACT FACT

作者: sim_cai | 来源:发表于2017-01-04 22:58 被阅读146次

    前言

    最近开始在研读《设计模式》一书,书中主要是以 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 代码

    欢迎讨论、批评、指错。

    相关文章

      网友评论

        本文标题:设计模式笔记及Swift上的实现之一『ABSTRACT FACT

        本文链接:https://www.haomeiwen.com/subject/zxpavttx.html