设计模式(Swift)-模板方法

定义

Template Method模式:

定义一个操作中的算法的骨架,而将一些步骤延迟到子类中。Template Method使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤。

动机

在软件构建过程中,对于某一项任务,它常常有稳定的整体操作结构,但各个子步骤却有很多改变的需求,或者由于固有的原因(比如框架与应用之间的关系)而无法和任务的整体结构同时实现。

如何在确定稳定操作结构的前提下,来灵活应对各个子步骤的变化或者晚期实现需求?

案例

假如有这样一套固定的流程

swift 复制代码
step1()

if step2() {
    step3()
}

let count = step4()

for _ in 0..<count {
    step5()
}

现在有个框架要封装这个功能,框架会实现step1step3step5的流程,但是step2step4并不由框架决定,而是由客户端开发者决定的。我们先可以顺着流程实现下,我们先实现下框架的接口:

方案一

swift 复制代码
class Library {
    func step1() {
        // do something
    }

    func step3() {
        // do something
    }

    func step5() {
        // do something
    }
}

然后我们可能需要一份文档,需要指明如何使用框架接口,所以最终结果应该是这样的:

swift 复制代码
class Application {
    func step2() -> Bool {
        return true
    }

    func step4() -> Int {
        return 3
    }
}

let app = Application()
let lib = Library()

lib.step1()
if app.step2() {
    lib.step3()
}

let count = app.step4()

for _ in 0..<count {
    lib.step5()
}

这种设计方案并不是很好,这些流程其实是框架的业务逻辑,现在客户端开发需要关注这些过程,这样不仅复杂,而且容易接错。我们改进一种方案,我们把接口设计成协议:

方案二

swift 复制代码
protocol Library {
    func step2() -> Bool
    
    func step4() -> Int
}

extension Library {
    
    func doWork() {
        step1()
        if step2() {
            step3()
        }
        
        let count = step4()
        
        for _ in 0..<count {
            step5()
        }
    }
    
    
    private func step1() {
        // do something
    }
    
    private func step3() {
        // do something
    }
    
    private func step5() {
        // do something
    }
}

现在,整个的业务流程已经封装到了doWork的方法里了,所以在客户端的开发里,我们只要遵守协议,然后实现协议方法就行:

swift 复制代码
class Application : Library {
    func step2() -> Bool {
        return true
    }

    func step4() -> Int {
        return 3
    }
}

let app = Application()
app.doWork()

这样看着就很简洁,我们看下有什么优势:

  • 客户端开发无需关注具体业务实现
  • 如果业务流程发生了改变,那么变化也只是Library内部的部分,不会影响了客户端的开发
  • 方案一的方法是Library早绑定Application,而方案二的方法是Library晚绑定Application。如何理解这句话,Library一定是早于Application开发的,但是在改进前,Library还需要依赖于Application的实现,改进后就不一样了,Library不依赖Application的实现,基于协议先把流程实现了。

虽然方案二挺好的,但是总觉得怪怪的,平时开发的时候不怎么能碰到这样的设计(这种方案是从C++那边翻译过来的),我们在改一种方案:

方案三

swift 复制代码
protocol LibraryDelegate: AnyObject {
    func step2() -> Bool
    
    func step4() -> Int
}

class Library {
    
    weak var delegate : LibraryDelegate?
    
    func doWork() {
        step1()
        if let result = delegate?.step2() , result {
            step3()
        }
        
        if let count = delegate?.step4() {
            for _ in 0..<count {
                step5()
            }
        }
        
    }
    
    private func step1() {
        // do something
    }
    
    private func step3() {
        // do something
    }
    
    private func step5() {
        // do something
    }
}


class Application : LibraryDelegate {
    func step2() -> Bool {
        return true
    }

    func step4() -> Int {
        return 3
    }
}

let app = Application()
let lib = Library()
lib.delegate = app
lib.doWork()

这种方案有没有觉得很熟悉,对,和UITableViewDataSource一样,UITableViewDataSource用的就是模版方法的设计模式。那方案三和方案二相比有什么优势?

  • 方案二的协议其实是一种继承(相当于C++的虚类继承),但方案三是组合,组合的耦合性是低于继承的
  • 职责明确,就像上面doWork方法,这个功能其实是框架的功能,但是在方案二中却变成了Application的功能。

总结

最后我们在看下模板方法的定义,看看通过案例是不是好理解一点。

Template Method模式是一种非常基础性的设计模式,在面向对象系统中有着大量的应用。它用最简洁的机制(协议的多态性)为很多应用程序框架提供了灵活的扩展点,是代码复用方面的基本实现结构。

除了可以灵活应对子步骤的变化外,"不要调用我,让我来调用你"的反向控制结构是Template Method的典型应用。

相关推荐
ITPUB-微风8 小时前
Service Mesh在爱奇艺的落地实践:架构、运维与扩展
运维·架构·service_mesh
大腕先生13 小时前
微服务环境搭建&架构介绍(附超清图解&源代码)
微服务·云原生·架构
文军的烹饪实验室13 小时前
处理器架构、单片机、芯片、光刻机之间的关系
单片机·嵌入式硬件·架构
猫头虎-人工智能14 小时前
NVIDIA A100 SXM4与NVIDIA A100 PCIe版本区别深度对比:架构、性能与场景解析
gpt·架构·机器人·aigc·文心一言·palm
阿里妈妈技术14 小时前
提效10倍:基于Paimon+Dolphin湖仓一体新架构在阿里妈妈品牌业务探索实践
架构
JAMES费15 小时前
figure机器人技术架构的演进初探——Helix人形机器人控制的革新
架构·机器人
程序员侠客行16 小时前
Spring事务原理详解 三
java·后端·spring·架构
WeiLai111220 小时前
面试基础--微服务架构:如何拆分微服务、数据一致性、服务调用
java·分布式·后端·微服务·中间件·面试·架构
菜鸟一枚在这1 天前
深入剖析抽象工厂模式:设计模式中的架构利器
设计模式·架构·抽象工厂模式
Swift社区1 天前
【微服务优化】ELK日志聚合与查询性能提升实战指南
spring·elk·微服务·云原生·架构