架构/设计

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

2023-03-02  本文已影响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

代码实现

  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

代码实现

  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

示例

新版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设计模式》
《大话设计模式》

上一篇 下一篇

猜你喜欢

热点阅读