iOS设计模式(二)、结构型设计模式

结构型设计模式

结构型设计模式主要用于处理类和对象的组合

适配器模式

  • 将一个原始接口转成客户端需要的接口
    • 原始接口--相当于Adaptee
    • 客户端需要接口(调用者) --- 相当于Target
  • 原始接口不兼容现在新的接口,让它们两个可以一起工作
    • 一起工作需要Adapter实现

角色分析:

  • 适配器
  • 目标接口
  • 被适配者

例如,在我们常见UITableView代码中:
  适配器是ViewController(实现两个协议)
  目标接口是UI界面->UITableView(Cell)
  被适配者是数据(Model)

这种时候,UIViewController中的代码会非常臃肿。这时候,我们可以创建一个Adapter类来做适配器,将tableView的dataSource和delegate赋值给Adapter对象,在Adapter中实现协议,完成数据的交互。 这样,我们就将ViewController和Model以及View都进行分离。而且,如果我们要在ViewController中关心cell的点击事件,也可以通过适配器传递。

适配器分为类适配器和对象适配器。

类适配器

类适配器中适配器继承于被适配者,适配器方法中调用父类的方法进行适配

如下例中,适配器Adapter将adaptee中的美元适配为人民币

//被适配者
class Adpatee{
    var dollar: Double{
        1000.0
    }
}
//目标接口
protocol Target{
    var RMB: Double{ get }
}
//适配器
class Adapter: Adpatee, Target{
    var RMB: Double{
        dollar * 7.0
    }
}
对象适配器

对象适配器中,Adapter遵守了Target协议,拥有一个adaptee的引用,当调用Adpater的适配方法时,内部实现对adaptee的方法进行适配。

如下例中,适配器Adapter将adaptee中的美元适配为人民币

//被适配者
class Adpatee{
    var dollar: Double{
        return 1000.0
    }
}
//目标接口
protocol Target{
    var RMB: Double{ get }
}
//适配器
class Adapter: Target{
    init(_ adaptee: Adpatee){
        self.adaptee = adaptee
    }
    var adaptee: Adpatee
    var RMB: Double{
        adaptee.dollar * 7.0
    }
}

桥接模式

  • 定义
    将抽象部分与实现部分分离,使它们都可以独立的变化。
  • 解决问题:
    类层级爆炸问题
  • 使用场景:
    • 开发中面临层级结构复杂(爆炸),可以使用桥接模式
    • 对不同的API之间进行桥接
    • 一个类存在两个独立的维度,且这两个维度都需要进行扩张(至少是两个维度)
  • 角色划分
    • 抽象部分
    • 实现抽象部分
    • 具体部分
    • 具体实现部分

下面是一个简单的例子,程序员作为抽象部分,程序员的工作内容为具体抽象部分,iOS、安卓、java程序员为具体部分,iOS、安卓、java工作内容为具体实现部分。

//程序员,抽象部分
protocol ComputerProgrammer{
    init(_ op: WorkOperation)
    var op: WorkOperation{ get }
    func work()
}
//具体工作(做什么),实现抽象部分
protocol WorkOperation{
    func operation()
}

//MARK: 具体抽象部分
class iosProgrammer: ComputerProgrammer{
    required init(_ op: WorkOperation) {
        self.op = op
    }
    var op: WorkOperation
    func work() {
        print("ios工程师开始工作")
        op.operation()
    }
}
class androidProgrammer: ComputerProgrammer {
    required init(_ op: WorkOperation) {
        self.op = op
    }
    var op: WorkOperation
    func work() {
        print("安卓工程师开始工作")
        op.operation()
    }
}
class javaProgrammer: ComputerProgrammer {
    required init(_ op: WorkOperation) {
        self.op = op
    }
    var op: WorkOperation
    func work() {
        print("Java工程师开始工作")
        op.operation()
    }
}

//MARK: 具体实现部分
class iosOperation: WorkOperation{
    func operation() {
        print("使用OC或Swift开发")
    }
}

class androidOperation: WorkOperation{
    func operation() {
        print("使用java或kotlin开发")
    }
}

class javaOperation: WorkOperation{
    func operation() {
        print("使用java开发")
    }
}


let ios = iosProgrammer(iosOperation())
ios.work()
let android = androidProgrammer(androidOperation())
android.work()
let java = javaProgrammer(javaOperation())
java.work()

打印结果:
ios工程师开始工作
使用OC或Swift开发
安卓工程师开始工作
使用java或kotlin开发
Java工程师开始工作
使用java开发

组合模式(Composite)

将对象组合成树形结构以表示部分-整体的层次结构,组合模式使得用户对单个对象和组合对象的使用具有一致性,用户可以统一的使用组合结构中的所有对象。

总结:使得用户对单个对象和组合最新的使用具有一致性。

最简单的最常见的例子就是UIView,典型的树形结构。

角色分析:

  • 抽象根节点(Component)
  • 具体子节点(Composite)
  • 叶子结点(Leaf)

Composite、Leaf指一类结点,并不是唯一,其中Leaf是无子结点(叶子结点)。

使用场景:
只允许使用,不允许继承。

组合模式最大的优点是它的结点可以自由增加,且调用结点方便。

//Component
@objc protocol TreeProtocol{
    var trees: Array<TreeProtocol> { get }
    
    func doSomething()
    @objc optional func addChild(_ child: TreeProtocol)
    @objc optional func removeChild(_ index: Int)
    @objc optional func getChildren(_ index: Int) -> TreeProtocol
    @objc optional func clear()
}

//Composite
class Tree: TreeProtocol{
    var trees: Array<TreeProtocol>
    
    init(){
        trees = Array<TreeProtocol>()
    }
    
    func doSomething() {
        
    }
    
    func addChild(_ child: TreeProtocol) {
        trees.append(child)
    }
    func removeChild(_ index: Int) {
        trees.remove(at: index)
    }
    func getChildren(_ index: Int) -> TreeProtocol {
        trees[index]
    }
    func clear() {
        trees.removeAll()
    }
    
}

//Leaf
class Leaf: TreeProtocol{
    var trees: Array<TreeProtocol>
    init(){
        trees = Array<TreeProtocol>()
    }
    
    func doSomething() {
        
    }
}

装饰模式(Decorator)

  • 定义
    动态地给一个对象添加一些额外的职能
  • 场景
    需要透明且动态地扩展类的功能时
  • 角色分析
    • 抽象组件
    • 具体组件
    • 抽象装饰者
    • 具体装饰者

下面的案例中,

  • 抽象组件是手机
  • 具体组件是iPhone11
  • 抽象装饰者是手机壳
  • 具体装饰者
    • 质量好的手机壳:耐磨、防水、防尘...(300)
      • 耐磨:wearProof()
      • 防水:waterProof()
      • 防尘:dustProof()
        GoodShell
    • 质量差的手机壳:耐磨(50)
      • 耐磨:wearProof()
        PoorShell
//抽象组件:手机
protocol MobilePhone{
    //装饰->手机壳
    func shell()
}
//具体组件:iPhone11
class Iphone11: MobilePhone {
    func shell() {
        print("苹果11")
    }
}
//抽象装饰者
class MobilePhoneShell: MobilePhone{
    private var mobile: MobilePhone
    
    init(_ mobile: MobilePhone) {
        self.mobile = mobile
    }
    func shell() {
        self.mobile.shell()
    }
}
//具体装饰者->好的手机壳
class GoodShell: MobilePhoneShell{
    func wearProof(){
        print("耐磨功能")
    }
    func waterProof(){
        print("防水功能")
    }
    func dustProof(){
        print("防尘功能")
    }
}
//具体装饰者->差的手机壳
class PoorShell: MobilePhoneShell{
    func wearProof(){
        print("耐磨功能")
    }
}

let phone = Iphone11()
phone.shell()

let good = GoodShell(phone)
good.shell()
good.dustProof()
good.waterProof()
good.wearProof()

let poor = PoorShell(phone)
poor.shell()
poor.wearProof()

//打印结果
苹果11
苹果11
防尘功能
防水功能
耐磨功能
苹果11
耐磨功能

动态地给一个对象添加一个额外的只能,就增加功能来说,Decorator模式相比生成子类更为灵活。

在OC中,我们可以通过Category来实现装饰模式。虽然通过Category可以实现装饰模式,但是这并不是一个严格的实现,由类别添加的方法是编译时绑定的,而装饰模式是动态绑定的,另外类别也没有封装被扩展类的实例。类别适合装饰器不多的时候。

外观模式(Facade)

  • 定义:门面模式
    • 要求一个子系统的外部和其内部的通信必须通过统一的对象进行
    • 外观模式提供了一个高层次接口,使得子系统更易于使用
  • 场景
    • 场景一:为复杂系统提供一个简单接口
    • 场景二:当我们构建一个层次结构的子系统时,使用Facede模式定义子系统中每一层入口点。如果子系统之间相互依赖,可以通过此模式简化它们之间的依赖关系。
  • 角色分析
    • 系统对外统一接口
    • 子系统接口

系统类使用UIImagePickerController为拍照提供了高层次接口,而内部是使用AVFoundation封装的,这种都是外观模式的体现。

下面案例我们使用手机来举例,手机提供了拍照、玩游戏、听音乐等高层次接口,而其内部都是调用各自的子系统来实现的。

class Iphone {
    
    private var camera: CameraProtocol?
    private var game: GameProtocol?
    private var music: MusicProtocol?
    
    init(camera: CameraProtocol? = nil,game: GameProtocol? = nil,music: MusicProtocol? = nil) {
        self.camera = camera
        self.game = game
        self.music = music
    }
    
    //相机
    func cameraAction(){
        //启动相机,开始拍照,关闭相机
        self.camera?.open()
        self.camera?.takePicture()
        self.camera?.close()
    }
    //玩游戏
    func palyGame(){
        //启动游戏,开始游戏,关闭游戏
        self.game?.open()
        self.game?.startGame()
        self.game?.close()
    }
    //听音乐
    func palyMusic(){
        //启动音乐,开始听歌,关闭音乐
        self.music?.open()
        self.music?.startMusic()
        self.music?.close()
    }
}
//相机模块接口
protocol CameraProtocol{
    func open()
    func takePicture()
    func close()
}

//具体实现类
class CameraImpl: CameraProtocol{
    func open() {
        print("打开相机")
    }
    func takePicture() {
        print("拍照")
    }
    func close() {
        print("关闭相机")
    }
}
//游戏模块接口
protocol GameProtocol{
    func open()
    func startGame()
    func close()
}
//游戏具体实现类
class GameImpl: GameProtocol{
    func open() {
        print("启动游戏")
    }
    func startGame() {
        print("开始游戏")
    }
    func close() {
        print("关闭游戏")
    }
}


//音乐模块接口
protocol MusicProtocol{
    func open()
    func startMusic()
    func close()
}
//具体音乐实现
class MusicImpl: MusicProtocol {
    func open() {
        print("打开音乐")
    }
    func startMusic() {
        print("播放音乐")
    }
    func close() {
        print("关闭播放")
    }
}

享元模式(FlyWeight)

  • 定义
    使用共享对象可以有效的支持大量的细粒度对象,强调:对象共享
  • 场景
    • 场景一:系统存在大量的相似对象
    • 场景二:需要缓冲池场景
  • 角色分析
    • 享元对象接口
    • 具体享元对象
    • 享元工厂(负责创建对象、管理对象)

例子:我们在一个界面上生成1000个花,花的种类一共六种(六种不同图片),如图所示


我们不用享元模式代码如下:

enum FlowerType: Int{
    case anemone,cosmos,gerberas,hollyhock,jasmine,zinnia,totalNumbersOfFlowerTypes
}

class ViewController: UIViewController{
    
    override func viewDidLoad() {
        super.viewDidLoad()
        for _ in 0 ..< 1000{
            autoreleasepool {
                let screenBounds = view.bounds
                let x = arc4random()%UInt32(screenBounds.width)
                let y = arc4random()%UInt32(screenBounds.height)
                
                let minSize = 10
                let maxSize = 50
                
                let WH = (Int(arc4random()) % (maxSize - minSize + 1)) + minSize
                
                let area = CGRect(x: Int(x), y: Int(y), width: WH, height: WH)
                
                let type = Int(arc4random()) % FlowerType.totalNumbersOfFlowerTypes.rawValue
                
                let imageView = flowerView(FlowerType(rawValue: type) ?? FlowerType.anemone)
                imageView.frame = area
                view.addSubview(imageView)
            }
        }
    }
    //根据类型获取
    func flowerView(_ type: FlowerType) -> UIImageView{
        var imageName = ""
        switch type {
        case .anemone:
            imageName = "anemone.png"
        case .cosmos:
            imageName = "cosmos.png"
        case .gerberas:
            imageName = "gerberas.png"
        case .hollyhock:
            imageName = "hollyhock.png"
        case .jasmine:
            imageName = "jasmine.png"
        case .zinnia:
            imageName = "zinnia.png"
        default:
            break
        }
        let image = UIImage(named: imageName)
        return UIImageView(image: image)
    }
}

如上面的代码所示,我们一共创建了1000个视图添加到view上,这样做的后果就是生成大量的对象,内存占用很大,特别图片如果也很大的话,进行绘制会耗费太多的GPU渲染。

从代码上看,其实只有6种花放在不同的位置而已,那我们可以利用享元模式思想,复用这两种花,然后绘制到不同位置,而不是增加对象添加到视图上。

enum FlowerType: Int{
    case anemone,cosmos,gerberas,hollyhock,jasmine,zinnia,totalNumbersOfFlowerTypes
}

//具体享元对象
class FlowerView: UIImageView{
    override func draw(_ rect: CGRect) {
        image?.draw(in: rect)
    }
}
//享元工厂
class FlowerFactory {
    //缓存池
    var flowerPool = [FlowerType : UIImageView]()
    //缓存池中获取,如果缓存池中没有,则创建一个
    func flowerView(_ type: FlowerType) -> UIImageView{
        
        var imageView = flowerPool[type]
        
        if imageView == nil {
            var imageName = ""
            switch type {
            case .anemone:
                imageName = "anemone.png"
            case .cosmos:
                imageName = "cosmos.png"
            case .gerberas:
                imageName = "gerberas.png"
            case .hollyhock:
                imageName = "hollyhock.png"
            case .jasmine:
                imageName = "jasmine.png"
            case .zinnia:
                imageName = "zinnia.png"
            default:
                break
            }
            let image = UIImage(named: imageName)
            imageView = FlowerView(image: image)
            
            flowerPool[type] = imageView!
        }
        return imageView!
    }
}

class FlyWeightView: UIView{
    var flowerList: Array<[NSValue : UIImageView]>?
    override func draw(_ rect: CGRect) {
        guard let flowerList = flowerList else {
            return
        }
        for dic in flowerList {
            guard let key = dic.keys.first,let imageView = dic.values.first else {
                continue
            }
            let area = key.cgRectValue
            imageView.draw(area)
        }
    }
}

class ViewController: UIViewController{
    
    override func viewDidLoad() {
        super.viewDidLoad()
        //使用享元模式
        let factory = FlowerFactory()
        
        var flowerList = Array<[NSValue:UIImageView]>()
        for _ in 0 ..< 1000{
            autoreleasepool {
                let type = Int(arc4random()) % FlowerType.totalNumbersOfFlowerTypes.rawValue
                //重复利用对象
                let flowerView = factory.flowerView(FlowerType(rawValue: type) ?? FlowerType.anemone)
                
                let screenBounds = view.bounds
                let x = arc4random()%UInt32(screenBounds.width)
                let y = arc4random()%UInt32(screenBounds.height)
                let minSize = 10
                let maxSize = 50
                let WH = (Int(arc4random()) % (maxSize - minSize + 1)) + minSize
                let area = CGRect(x: Int(x), y: Int(y), width: WH, height: WH)

                let key = NSValue(cgRect: area)
                let dictionary = [key : flowerView]
                flowerList.append(dictionary)
            }
        }
        
        let flyWeightView = FlyWeightView(frame: view.bounds)
        flyWeightView.flowerList = flowerList
        
        self.view = flyWeightView
    }
    
}

可以明显看到内存已经降下来了,我们只是生成了对象flowerView,但是并没有add到FlyweightView上,我们使用image重新绘制了一个新的位置去显示。

tableViewCell的重用机制就是实现了享元模式,在要使用一个cell的时候,会先去重用池中查询是否有可重用的cell,如果有则重用,没有就创建一个,这就是享元模式。

享元模式最主要有两个关键组件,可共享的享元对象和保存它们的享元池

代理模式

  • 定义
    为其它对象提供一种代理,控制对这个对象的访问。
  • 角色分析
    • 代理对象
      • 持有目标对象引用
      • 实现目标接口
    • 目标接口
    • 具体目标对象
  • 变种:动态代理(Java)、静态代理

比如我们让代购的人帮忙购买手机

//目标接口,代购iPhone
protocol PersonProtocol{
    //下单(选购)
    func buyProduct()
    //支付
    func payProduct()
}

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