界说

Template Method形式:

界说一个操作中的算法的骨架,而将一些进程延迟到子类中。Template Method使得子类能够不改动一个算法的结构即可重界说该算法的某些特定进程。

动机

软件构建进程中,对于某一项任务,它常常有安稳的整体操作结构,但各个子进程却有许多改动的需求,或许由于固有的原因(比如结构与使用之间的关系)而无法和任务的整体结构一起完成。

如安在确认安稳操作结构的前提下,来灵敏应对各个子进程的改变或许晚期完成需求?

案例

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

step1()
if step2() {
    step3()
}
let count = step4()
for _ in 0..<count {
    step5()
}

现在有个结构要封装这个功用,结构会完成step1step3step5的流程,但是step2step4并不由结构决定,而是由客户端开发者决定的。咱们先能够顺着流程完成下,咱们先完成下结构的接口:

计划一

class Library {
    func step1() {
        // do something
    }
    func step3() {
        // do something
    }
    func step5() {
        // do something
    }
}

然后咱们可能需求一份文档,需求指明怎么使用结构接口,所以终究成果应该是这样的:

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()
}

这种规划计划并不是很好,这些流程其实是结构的事务逻辑,现在客户端开发需求重视这些进程,这样不仅杂乱,并且简单接错。咱们改善一种计划,咱们把接口规划成协议:

计划二

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的办法里了,所以在客户端的开发里,咱们只要遵守协议,然后完成协议办法就行:

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++那边翻译过来的),咱们在改一种计划:

计划三

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的典型使用。