美文网首页架构/设计
独孤九剑--设计模式(iOS结构型篇)

独孤九剑--设计模式(iOS结构型篇)

作者: _小沫 | 来源:发表于2023-03-02 17:58 被阅读0次

    独孤九剑--设计模式(iOS创建型篇)
    独孤九剑--设计模式(iOS行为型篇)

    适配器模式 Adapter Pattern

    情景

    Apple公司维护了其员工管理系统,员工数据如下:

    struct AppleEmployee {
        var name: String
        var seniority: Int
        var salary: Double
        
        func getEmployeeInfo() -> String {
            return "员工:\(name), 工龄:\(seniority), 薪酬:\(salary)"
        }
    }
    

    现在Apple公司收购了Spaces公司,Spaces公司也有一套自己的员工管理系统,员工数据如下;显而易见的是,不管从数据结构,还是提供的功能来说,两者完全不兼容;

    struct SpacesEmployee {
        var s_name: String
        var s_seniority: Int
        var s_salary: Int
    }
    

    现在Apple需要将Spaces员工接入到自己的系统;

    简单粗暴的做法有2种:

    1. 直接修改Spaces员工系统所有源码,重构成Apple系统要求的数据结构、方法调用;
      对于庞大的系统而言,这种改法工作量巨大而且就容易出错;

    2. 在使用Apple员工功能的地方,都加一个对应的Spaces功能;
      以系统中的一个获取员工信息的功能为例

    // 原有系统的功能
    func dealEmployee(employee: AppleEmployee) {
        // ...
        print(employee.getEmployeeInfo())
    }
    

    Spaces员工也需要支持该功能,重写一样的功能:

    func dealEmployee(employee: SpacesEmployee) {
        // ...
        let spacesEeInfo = "员工:\(employee.s_name), 工龄:\(employee.s_seniority), 薪酬:\(Double(employee.s_salary))"
        print(spacesEeInfo)
    }
    

    这种方式相比第一种方案工作量小一点点,但代码同样很难维护;
    (这还是基于Swift语言的方法重载而言,如果是OC那将会比这个更复杂,不能使用重载那OC就需要判断到底是哪个类型,增加一系列if else)

    破解招式

    适配器模式

    将一个接口转换成客户希望的另一个接口,从而使原本接口不匹配而无法一起工作的两个类能够在一起工作。

    适配器模式又分为2种:类适配器模式,对象的适配器模式

    类适配器

    类适配器模式是声明一个公共接口,通过继承的方式把适配的类的API转换成为目标类的API。

    UML类图
    class adapter

    Target:目标抽象类;Adapter:适配器类;Adaptee:适配者类

    代码实现
    1. 提取adapter、adaptee需要用到的公共属性、方法,封装成接口/protocol
    protocol Employee {
        var salary: Double { get }
        func getEmployeeInfo() -> String
    }
    
    1. 扩展target (AppleEmployee),实现Employee协议
    // target
    extension AppleEmployee : Employee {
            // Employee协议接口都是根据AppleEmployee 提取的; 因此原本就已经实现协议方法了
    }
    
    1. 新建adapter类,继承需要适配的adaptee并实现Employee协议;(如果是C++这种支持多继承的语言,adapter可以继承adaptee和target)
    class SpacesEmployeeClassAdapter : SpacesEmployee, Employee {
        var salary: Double {
            return Double(self.salary)
        }
        
        func getEmployeeInfo() -> String {
            return "员工:\(self.s_name), 工龄:\(self.s_seniority), 薪酬:\(Double(self.s_salary))"
        }
    }
    
    1. 重构代码,AppleEmployee都替换成Employee;需要接入SpacesEmployee的使用其子类SpacesEmployeeClassAdapter
    let appleEmployee = AppleEmployee(name: "jobs", seniority: 20, salary: 100000)
    dealEmployee(employee: appleEmployee)
    let employeeAdapter = SpacesEmployeeClassAdapter(s_name: "jack", s_seniority: 1, s_salary: 5000)
    dealEmployee(employee: employeeAdapter)
    func dealEmployee(employee: Employee) {
        // ...
        print(employee.getEmployeeInfo())
    }
    

    对象适配器

    与类适配器不同,对象适配器不继承被适配者,而是组合了一个对其引用。

    UML类图

    类适配器中,由于语言不支持多继承,创建了公有协议,变相的也更改了使用Target的具体类(AppleEmployee)的地方;对象适配器,适配器不需要继承被适配者,现在就可以将适配器对象直接继承自Target的具体类,公有协议也无需创建;

    instance adapter
    代码实现
    1. 新建adapter对象SpacesEmployeeInstanceAdapter, 继承自AppleEmployee;adapter内引用adaptee对象,重写AppleEmployee方法;
    class SpacesEmployeeInstanceAdapter : AppleEmployee {
        var spacesEmployee: SpacesEmployee
        
        init(spacesEmployee: SpacesEmployee) {
            self.spacesEmployee = spacesEmployee
            super.init(name: spacesEmployee.s_name, seniority: spacesEmployee.s_seniority, salary: Double(spacesEmployee.s_salary))
        }
        
        override func getEmployeeInfo() -> String {
            return "员工:\(spacesEmployee.s_name), 工龄:\(spacesEmployee.s_seniority), 薪酬:\(self.salary)"
        }
    }
    
    1. 需要接入SpacesEmployee的使用SpacesEmployeeInstanceAdapter
    let appleEmployee = AppleEmployee(name: "jobs", seniority: 20, salary: 100000)
    dealEmployee(employee: appleEmployee)
    let spacesEmployee = SpacesEmployee(s_name: "jack", s_seniority: 1, s_salary: 5000)
    let employeeAdapter = SpacesEmployeeInstanceAdapter(spacesEmployee: spacesEmployee)
    dealEmployee(employee: employeeAdapter)
    func dealEmployee(employee: AppleEmployee) {
        // ...
        print(employee.getEmployeeInfo())
    }
    

    对象适配器和类适配器对比

    适配器模式使用场景

    适配器模式属于是被动设计的,使用适配器模式一般都是发现系统现有类的接口不符合系统的需要,或者多套相同功能系统间不兼容,需要更优雅实现而引入;如果在最开始就已经知道后续的需求,那完全可以在当时就设计好接口、数据结构等;

    另一个场景是,使用不同三方库的相同功能时,当无法修改他们的源码,适配器模式也会是一个很好的处理方式

    适配器模式在iOS系统中的使用

    iOS中的委托(delegate、dataSource)按其用途来说其实是使用了对象适配器模式的;实现代理协议的具体类就是个适配器;
    以UITableView为例,tableView需要多少行的代码:

    tableView. dataSource=vc;
    
    // vc
    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
        return self.datas.count;
    }
    

    vc就是adapter,它实现了tableView的numberOfRowsInSection接口要求,适配了tableView;datas就相当于adaptee,vcnumberOfRowsInSection的具体实现中,调用了datas而得到真正需要的数据;

    桥接模式 Bridge

    情景

    App需要新增一个分享功能;

    1. 第一个版本需求是分享文字到v信;
      简单的面向对象代码:
    // v信分享平台
    struct WXSharePlatform {
        func shareText(text: String) {
            // WXApi ....
            // ...
            print("\(text) v信分享成功")
        }
    }
    
    // 文字分享类
    struct WXTextShare {
        var text: String
        private let wxPlatform = WXSharePlatform()
        
        func share() {
            wxPlatform.shareText(text: text)
        }
    }
    

    客户端分享调用:

    let share = WXTextShare(text: "心灵毒鸡汤...")
    share.share()
    

    实现的还行。。。

    1. 迭代需求:需要支持对图片内容的分享,同时分享平台还要支持QQ;
      还是一样的套路,新建对应的类即可:
    // QQ平台
    struct QQSharePlatform {
        func shareText(text: String) {
            // QQApi ....
            print("\(text) QQ分享成功")
        }
        
        func shareImage(imageData: Data) {
            // QQApi ....
            print("图片 QQ分享成功")
        }
    }
    
    // 新增wx图片分享、新增qq文字分享、新增qq图片分享 处理类
    struct WXImageShare {
    ....
    }
    
    struct QQTextShare {
    ...
    }
    
    struct QQImageShare {
    ...
    }
    

    忍忍还是能勉强接受。。。

    1. 迭代需求,需要支持对视频内容的分享
      。。。
    2. 迭代需求,需要支持微博平台
      。。。

    我想你已经疯了,也意识到问题所在:
    随着功能不断增加,类层级爆炸式增长,而且这些类大部分代码都是重复的;

    破解招式

    桥接模式

    将抽象部分与它的实现部分分离,使它们都可以独立地变化

    以上示例,存在两个维度:1. 分享内容,2.分享平台;
    使用桥接模式需要做的,就是将分享内容分享平台分离,使他们独立变化;

    UML类图

    bridge
    • Abstraction:定义抽象接口,拥有一个Implementor类型的对象引用
    • RefinedAbstraction:扩展Abstraction中的接口定义
    • Implementor:具体实现的接口
    • ConcreteImplementor:实现Implementor接口,给出具体实现

    代码实现

    1. 抽象出分享平台实现类接口
    // implementor
    protocol SharePlatform {
        func shareText(text: String)
        
        func shareImage(imageData: Data)
    }
    
    1. 重构分享平台实现类
    // ConcreteImplementor
    // 内部实现不变
    struct WXSharePlatform : SharePlatform {
        func shareText(text: String) {
    ...
        }
        
        func shareImage(imageData: Data) {
    ...
        }
    }
    
    struct QQSharePlatform : SharePlatform {
        func shareText(text: String) {
    ...
        }
        
        func shareImage(imageData: Data) {
    ...
        }
    }
    
    1. 定义分享内容的抽象接口
    // Abstraction
    protocol ShareContent {
        var implementor: SharePlatform { get set }
        
        func share()
    }
    
    1. 扩展分享内容接口定义
    // RefinedAbstraction
    struct TextShare : ShareContent {
        var implementor: SharePlatform
        var text: String
        
        func share() {
            implementor.shareText(text: text)
        }
    }
    
    struct ImageShare : ShareContent {
        var implementor: SharePlatform
        var imageData: Data
        
        func share() {
            implementor.shareImage(imageData: imageData)
        }
    }
    

    客户端调用分享:

    let wx = WXSharePlatform()
    let share1 = TextShare(implementor: wx, text: "心灵毒鸡汤...")
    share1.share()
    let imgData = ...
    let qq = QQSharePlatform()
    let share2 = ImageShare(implementor: qq, imageData: imgData)
    share2.share()
    

    当需要增加一类分享内容,或新增一类分享平台,都只需要增加一个类即可;具体的操作都通过Abstraction与Implementor组合实现

    适用场景

    • 扩展相同功能时,不断的新增类导致的类层级爆炸问题;可以采用桥接模式
    • 如果出现抽象部分和实现部分都应该可以扩展的情况,可以采用桥接模式
    • 如果你不希望在抽象和实现部分采用固定的绑定关系,可以采用桥接模式

    装饰模式 Decorator Pattern

    情景

    开发一套网上商城功能:售卖指定品类的物品,每类物品价格固定;选择商品后给出商品信息及其价格;

    简单实现:
    抽象商品公有接口

    protocol Goods {
        var name: String { get }
        var price: Double { get }
    }
    
    extension Goods {
        func sold() {
            print("\(name), price:\(price)")
        }
    }
    

    为每种商品新建对应对象

    class Flower: Goods {
        var name: String = "鲜花"
        var price: Double = 99.9
    }
    
    class Chocolate: Goods {
        var name: String = "巧克力"
        var price: Double = 66
    }
    

    使用

    let flower = Flower()
    flower.sold()
    // 鲜花, price:99.9
    

    需求迭代:适逢节日,商城为每种商品提供有偿的礼物包装和贺卡销售,需要编码支持;
    简单。。。在原有商品基础上继承一个新类即可:

    // 商品= 鲜花+礼物包装
    class FlowerWithGift: Flower {
        private let giftPrice = 5.0
        
        override init() {
            super.init()
            name = super.name + "+" + "礼物包装"
            price = super.price + giftPrice
        }
    }
    
    class FlowerWithCard: Flower {...}
    class ChocolateWithGift: Chocolate {...}
    class ChocolateWithCard: Chocolate {...}
    

    选择了鲜花并且需要包装的:

      let flowerGift = FlowerWithGift()
      flowerGift.sold()
    // 鲜花+礼物包装, price:104.9
    

    如果消费者选择鲜花并且需要包装的,同时还要求提供贺卡呢?
    好像写的类还不满足,那就再加一个:

    class FlowerWithGiftCard: FlowerWithGift {...}
    

    如果再增加一个商品附属品呢,按照组合计算,那每个商品类就都需要2^3-1=7个子类了。。。

    破解招式

    装饰模式

    动态地给一个对象添加一些额外的职责。就扩展功能来说,装饰模式相比生成子类更为灵活

    UML类图

    decorator.png
    • Component:给出一个抽象接口,以规范准备接受附加责任的对象。
    • ConcreteComponent:定义一个将要接收附加责任的类。
    • Decorator:持有一个构件(Component)对象的实例,并定义一个与抽象构件接口一致的接口。
    • ConcreteDecorator:负责给构件对象添加附加职责。

    代码实现

    1. 抽象商品附属品协议,继承自原有商品协议
    // Decorator
    // 商品附属品
    protocol GoodsAttached: Goods {
        var goods: Goods { get }
    }
    
    1. 礼物包装、贺卡单独封装 具体实现
    // concreteDecorator
    // 礼物包装
    struct Gift: GoodsAttached {
        private let giftPrice = 5.0
        
        var goods: Goods
        
        var name: String {
            return goods.name + "+" + "礼物包装"
        }
        
        var price: Double {
            return goods.price + giftPrice
        }
    }
    
    // 贺卡
    struct Card: GoodsAttached {
        private let cardPrice = 1.1
        
        var goods: Goods
        
        var name: String {
            return goods.name + "+" + "贺卡"
        }
        
        var price: Double {
            return goods.price + cardPrice
        }
    }
    
    1. 原有商品类Component、ConcreteComponent不做更改;
    2. 商品+礼物包装+贺卡 调用
    let flower = Flower()
    let giftWrap = Gift(goods: flower)
    let cardWrap = Card(goods: giftWrap)
    cardWrap.sold()
    // 鲜花+礼物包装+贺卡, price:106.0
    

    后续再增加附属品时,只要增加一个对应的concreteDecorator类即可

    适用场景

    • 用继承扩展功能不太现实的情况下,应该考虑用组合的方式
    • 透明并且动态地给对象增加新的职责

    外观模式 Facade Pattern

    为子系统中的一组接口提供一个一致的界面,Facade模式定义了一个高层接口,这个接口使得这一子系统更加容易使用。

    相对比较简单

    facade.png
    // facade
    struct System {
        let sa = SystemA()
        let sb = SystemB()
        let sc = SystemC()
        
        func start() {
            // 封装调用子系统
            sa.open()
            sb.turnOn()
            sc.run()
        }
    }
    

    代理模式 Proxy Pattern

    为其他对象提供一种代理,以控制对这个对象的访问

    UML类图

    proxy.png
    • Subject:抽象主题角色,声明了目标对象和代理对象的共同接口,这样一来在任何可以使用目标对象的地方都可以使用代理对象。
    • RealSubject:具体主题角色,被代理对象,定义了代理对象所代表的目标对象。
    • Proxy:代理类,代理对象内部含有目标对象的引用,从而可以在任何时候操作目标对象;代理对象提供一个与目标对象相同的接口,以便可以在任何时候替代目标对象。代理对象通常在客户端调用传递给目标对象之前或之后,执行某个操作,而不是单纯地将调用传递给目标对象。

    示例

    新版mac只有美丽国才能买到,在中国需要购买就需要通过代购(代理Proxy);

    1. 创建购买者抽象对象接口
    protocol Buyer {
        func buyMac()
    }
    
    1. 创建真实的购买者对象
    struct ChinaBuyer: Buyer {
        func buyMac() {
            print("china buyer buy Mac")
        }
    }
    
    1. 创建代理对象
    struct BuyerProxy: Buyer {
        let buyer = ChinaBuyer()
        func buyMac() {
            buyer.buyMac()
            print("from usa")
        }
    }
    

    调用:

    let proxy = BuyerProxy()
    proxy.buyMac()
    

    应用

    代理模式的应用形式:

    • 远程代理:即为一个对象在不同的地址空间提供局部代表,这样可以隐藏一个对象存在于不同地址空间的事实;
    • 虚拟代理:即根据需要创建开销很大的对象,通过它来存放实例化需要很长时间的真实对象;
    • 安全代理:用来控制真实对象访问时的权限;
    • 智能指引:即当调用真实对象时,代理处理另外一些事。

    iOS系统中的代理模式

    说到代理,iOS开发立马就想到delegate,dataSource;
    但实际上,他们并不是代理模式,只能称为委托,实质上还是归为适配器模式(参考上面适配器模式);

    代理模式和对象适配器模式,从UML图也可以看出,他们是及其类似的;有区别的是代理模式中RealSubject也需要实现同一个接口,而适配器中Adaptee无限制;根据实际应用场景也很容易分辨出来;

    而iOS中真正使用到代理模式的NSProxy,NSProxy是个抽象基类,使用时需子类化;
    具体可以根据YYKit的YYWeakProxy分析:

    @interface YYWeakProxy : NSProxy
    
    /**
     The proxy target.
     */
    @property (nullable, nonatomic, weak, readonly) id target;
    
     // return A new proxy object.
    - (instancetype)initWithTarget:(id)target;
    ...
    @end
    

    YYWeakProxy对应proxy,target就是RealSubject;
    proxy重写了forwardingTargetForSelector等动态转发方法,当给proxy发送消息,基于oc runtime机制会将消息转发给target;实现了代理作用;

    (YYWeakProxy这种代理模式,主要是为了解决内存循环引用问题)


    完整代码


    参考:
    《Objective-C编程之道》
    《精通Swift设计模式》
    《大话设计模式》

    相关文章

      网友评论

        本文标题:独孤九剑--设计模式(iOS结构型篇)

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