美文网首页
设计模式-结构型

设计模式-结构型

作者: 极客学伟 | 来源:发表于2023-02-12 17:05 被阅读0次

    设计模式-结构型

    结构型设计模式包含:代理模式、适配器模式、桥接模式、装饰模式、外观设计模式、享元模式、组合模式

    代理模式

    核心是在具体的功能类与使用者之间建立一个中介类作为代理,使用者通过代理对象对真实的功能类进行访问。
    在iOS开发中,代理设计模式非常有用,在UIKit框架中,UITableViewUITextView 等组件的渲染和交互都采用了代理设计模式。

    以病人预约看病的软件设计举例,核心功能类只有两个医生类和病人类,病人看病前首先预约,预约完成后问诊,医生陈述病情,然后开药。整个系统中有些行为既不属于病人类也不属于医生类,如医生的预约和问诊过程的控制等,这时就需要一个代理类代理医生处理这些行为。

    重构后

    class Patient {
        func describeCondition() -> String {
            let describe = "描述病情"
            print(describe)
            return describe
        }
    }
    class Doctor {
        func writPrescription(condition: String) -> String {
            let prescription = "依据病情: \(condition), 开的处方"
            print(prescription)
            return prescription
        }
    }
    class DoctorProxy {
        var patient: Patient
        init(patient: Patient) {
            self.patient = patient
        }
        func seeDoctor() {
            // 预约医生
            let doctor = reservation()
            // 病人描述病情
            let condition = self.patient.describeCondition()
            // 医生开处方
            doctor.writPrescription(condition: condition)
        }
        func reservation() -> Doctor {
            let doctor = Doctor()
            print("预约医生")
            return doctor
        }
    }
    let patient = Patient()
    let doctorProxy = DoctorProxy(patient: patient)
    doctorProxy.seeDoctor()
    

    其中,病人并没有和医生进行直接交互,而是通过中间的代理类 DoctorProxy。实际开发中,使用代理设计模式可以使具体的功能类的聚合性更强,并可以在某些功能的执行前后进行额外的准备工作和善后工作。

    适配器模式

    适配器模式并不是软件设计中的最佳实践,其主要为了解决软件开发过程中新旧模块不兼容的问题。其定义:将一个类的接口转换成使用者期望的另外接口,使得原本接口不兼容的类可以一起工作。

    当数据模型版本升级时,可以使用适配器模式兼容旧的数据模型

    重构后

    class User {
        var name: String
        var age: Int
    }
    class UserV2 {
        var nickName: String
        var age: Int
        var address: String
    }
    class UserAdapter {
        static func toUserV2(user: User) -> UserV2 {
            return UserV2(nickName: user.name, age: user.age, address: "")
        }
    }
    let user = User(name: "学伟", age: 18)
    let userV2 = UserAdapter.toUserV2(user: user)
    print(userV2)
    

    实际开发中,由于数据模型升级造成的代码不兼容问题会经常遇到,当项目过于庞大时,如果贸然修改以往的旧代码,会有很大的工作量,同时也会伴随很大的风险,使用适配器模式就是一种比较适合的折中选择。

    桥接模式

    桥接模式是合成复用原则的一种应用,其核心是将抽象与实现分离,用组合来代替继承关系,从而给类更多的扩展性,降低类之间的耦合度。
    实际开发中,当某个类具有多维度的属性时,在组织类的结构时,使用桥接模式十分适合。
    例如:汽车从功能上分为轿车和卡车,颜色上又分为黑色白色。在设计时有两种设计方案:一种是创建轿车和卡车的类,每个类包含颜色属性:

    enum Color {
        case red
        case green
    }
    class Car {
        var color: Color
    }
    class Saloon: Car {
        print("我是轿车")
    }
    class Truck: Car {
        print("我是卡车")
    }
    

    另外一种设计方案可以根据桥接模式,根据实际需要对功能和颜色进行组合。

    重构后

    enum Color {
        case red
        case green
    }
    enum CarType {
        case saloon
        case truck
        var name: String {
            switch self {
            case .saloon:
                return "轿车"
            case .truck:
                return "卡车"
            }
        }
    }
    protocol CarProtocol {
        var color: Color { get }
        var carType: CarType { get }
        func log()
    }
    extension CarProtocol {
        func log() {
            print("我是" + carType.name)
        }
    }
    class Car: CarProtocol {
        var color: Color
        var carType: CarType
        init(color: Color, carType: CarType) {
            self.color = color
            self.carType = carType
        }
    }
    let car = Car(color: .red, carType: .saloon)
    car.log()
    

    通过组合颜色和类型两个枚举来构建汽车对象,避免了因继承带来的耦合问题。

    装饰模式

    在不改变对象结构的情况下,为该对象增加一些功能。
    类比现实生活中的:手机壳、壁画...

    以为墙添加贴纸的逻辑设计为例:

    重构后

    protocol WallProtocol {
        func printInfo()
    }
    class Wall: WallProtocol {
        func printInfo() {
            print("墙面")
        }
    }
    class StickerDecorator: WallProtocol {
        var wall: Wall
        init(wall: Wall) {
            self.wall = wall
        }
        func printInfo() {
            print("贴纸装饰")
            self.wall.printInfo()
        }
    }
    let wall = Wall()
    let stickerDecorator = StickerDecorator(wall: wall)
    stickerDecorator.printInfo()
    

    其中 StickerDecorator 即装饰器,也需要完整的实现功能类所实现的接口,这样才能不会改变被装饰对象的原始行为。
    使用装饰模式可以理解成:为对象的行为进行扩展,只是相比较于继承,装饰模式更加灵活、类之间的耦合度也更低。同时,装饰模式可能由于过度设计而增加过多装饰器类,使系统复杂性变高。

    外观设计模式

    在软件设计中,当一个系统的功能越来越强时,子模块会越来越多,应用端对系统的访问也会越来越复杂。这时可以通过提供一个外观类来统一处理这些交互,降低应用端使用的复杂性。
    以客户购买商品流程的设计为例:

    struct User {
        var name: String
    }
    struct Goods {
        static func choseGoods(user: User) {
            print("\(user.name)选择商品")
        }
    }
    struct Cashier {
        static func pay(user: User) {
            print("\(user.name)付款")
        }
    }
    struct Package {
        static func packing(user: User) {
            print("\(user.name)打包")
        }
    }
    let user = User(name: "学伟")
    Goods.choseGoods(user: user)
    Cashier.pay(user: user)
    Package.packing(user: user)
    

    User需要完成一个购物流程需要同时与 GoodsCashierPackage 三个类进行交互。当每个模块都变得越来越复杂时,代码的扩展和维护将变得十分困难。
    对于这样的场景,可以定义一个外观类来统一处理用户的购物逻辑。

    重构后

    ...
    struct Store {
        static func shop(user: User) {
            Goods.choseGoods(user: user)
            Cashier.pay(user: user)
            Package.packing(user: user)
        }
    }
    let user = User(name: "学伟")
    Store.shop(user: user)
    

    其中,Store 起到外观的作用,顾客只需要与 Store 一个类进行交互即可,

    享元模式

    运用共享技术实现大量细粒度对象的复用,避免大量重复对象造成系统的资源开销。
    在享元模式中,需要根据共享性将对象中的数据拆分成内部状态和外部状态,之后将内部状态封装成享元对象用户共享。享元模式会增加系统的复杂度,对于不会产生大量重复对象的系统并不适用。

    以黑白棋设计为例:

    struct Place {
        var x: Int
        var y: Int
    }
    enum Color {
        case White
        case Black
    }
    class ChessPiece {
        var place: Place
        var color: Color
        var radius: Double
        init(place: Place, color: Color, radius: Double) {
            self.place = place
            self.color = color
            self.radius = radius
        }
    }
    

    一个棋子除了位置不同外,颜色和半径对于大部分棋子来说是相同的,这种场景下,place 就是 外部状态,color与radius为内部状态,可以使用享元模式重构

    重构后

    struct Place {
        var x: Int
        var y: Int
    }
    enum Color {
        case White
        case Black
    }
    class ChessPieceFlyweight {
        var color: Color
        var radius: Double
        init(color: Color, radius: Double) {
            self.color = color
            self.radius = radius
        }
    }
    class ChessPieceFlyweightFactory {
        static let white = ChessPieceFlyweight(color: .White, radius: 16.0)
        static let black = ChessPieceFlyweight(color: .Black, radius: 16.0)
        static func getChessPieceFlyweight(color: Color) -> ChessPieceFlyweight {
            switch color {
            case .White:
                return white
            case .Black:
                return black
            }
        }
    }
    class ChessPiece {
        var place: Place
        var chessPieceFlyweight: ChessPieceFlyweight
        init(place: Place, color: Color) {
            self.place = place
            self.chessPieceFlyweight = ChessPieceFlyweightFactory.getChessPieceFlyweight(color: color)
        }
    }
    

    即便创建若干个棋子,真实的 ChessPieceFlyweight 只有两个,随着创建的个数越多,节省的内存也越多。

    组合模式

    采用树状层级结构来表示部分与整体的关系,使得无论是整体对象还是单个对象,对其访问都具有一致性。
    在面向对象设计思想中,完整的文件系统至少需要两个类来描述,文件夹和文件;文件系统实际就是树状层级结构,可以使用组合模式设计。

    重构后

    enum NodeType {
        case Folder
        case File
    }
    protocol FileNode {
        var type: NodeType { get }
        var name: String { get }
        func addNode(node: FileNode)
        func removeNode(node: FileNode)
        func getAllNode() -> [FileNode]
    }
    class file: FileNode {
        var type: NodeType
        var name: String
        var child = [FileNode]()
        init(type: NodeType, name: String) {
            self.type = type
            self.name = name
        }
        func addNode(node: FileNode) {
            self.child.append(node)
        }
        
        func removeNode(node: FileNode) {
            self.child = self.child.filter({ n in
                if node.name == n.name && node.type == n.type {
                    return false
                }
                return true
            })
        }
        
        func getAllNode() -> [FileNode] {
            return self.child
        }
    }
    

    通过定义统一的 FileNode 接口,使得使用方无论关心当前操作的节点是文件夹还是文件,都有统一的访问方式,而且屏蔽了树结构中层级的概念,这是组合模式最大的优势。

    相关文章

      网友评论

          本文标题:设计模式-结构型

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