什么是模版方法模式(Template Method Pattern)
模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中实现。它允许子类在不改变算法结构的情况下,重新定义算法中的某些特定步骤。
想象一下制作饮料的过程:无论是做咖啡还是茶,基本步骤可能是相似的:烧水、冲泡、倒入杯中、加调料。模板方法就像是这个制作流程的“说明书”(算法骨架),规定了必须执行哪些步骤以及它们的顺序。但是,“冲泡”这一步具体是用咖啡粉还是茶叶,“加调料”这一步是加糖奶还是柠檬,则由具体的“咖啡制作说明”或“茶制作说明”(子类)来决定。
模板方法模式的核心在于固定算法框架,允许子类定制细节。
核心思想/步骤
- 定义模板方法: 在一个抽象基类(Abstract Class)中,定义一个 final 的方法,称为“模板方法”(Template Method)。这个方法内部按特定顺序调用一系列其他方法,这些方法构成了算法的骨架。
- 区分步骤类型: 模板方法中调用的方法通常分为两类:
- 抽象方法 (Abstract Methods / Primitive Operations): 这些方法在基类中声明为 abstract,没有具体实现。子类 必须 提供自己的实现。这些是算法中变化的部分。
- 具体方法 (Concrete Methods): 这些方法在基类中已经提供了默认实现。子类可以继承使用,通常代表算法中不变的部分。
- 钩子方法 (Hook Methods): 这些是基类中提供的具有默认(通常是空实现或简单实现)的具体方法。子类 可以 选择性地覆盖(override)它们,以在算法的特定点“挂钩”额外的行为,或者控制算法流程的某些可选部分。这提供了更大的灵活性。
- 子类实现: 创建具体的子类(Concrete Class),继承自抽象基类。子类需要实现所有抽象方法,并可以选择性地覆盖钩子方法,以完成算法中特定于子类的步骤。
- 执行流程: 客户端代码通过调用抽象基类引用指向的具体子类实例的模板方法来启动算法。算法的整体结构由基类的模板方法控制,而具体步骤的实现则由子类提供。
优点
- 代码复用 (Code Reuse): 将算法中不变的部分提取到基类的模板方法和具体方法中,避免了代码重复。
- 封装不变部分,扩展可变部分: 算法的整体结构被固定在基类中,不易被修改,保证了流程的稳定性。同时,将易变的部分(抽象方法、钩子方法)开放给子类去实现或修改,提高了系统的扩展性。
- 行为控制反转 (Inversion of Control): 基类(父类)调用子类的方法来完成特定步骤,而不是子类调用基类。这体现了一种控制反转的思想,框架(基类)控制了执行流程。
- 遵循开闭原则 (Open/Closed Principle): 对扩展开放(可以通过增加新的子类来实现新的算法变种),对修改关闭(不需要修改基类的模板方法即可扩展)。
可能的缺点
- 子类数量增加: 每一种算法的不同实现都需要一个单独的子类,可能导致类的数量增多。
- 可读性/维护性: 如果模板方法中的步骤过多,或者钩子方法的使用比较复杂,可能会使得理解算法的整体流程和子类的具体实现变得困难。
- 对子类的约束: 子类必须遵循基类定义的算法结构,有时可能限制了子类的灵活性。
使用场景
- 当多个类有共同的行为,并且这些行为可以用一个固定的算法骨架来表示时,可以将共同的部分提取到基类,不同的部分由子类实现。
- 当需要控制子类的扩展点,只允许在特定步骤进行定制时。
- 重构:当你发现几个类执行相似的步骤,但顺序或细节略有不同时,可以考虑使用模板方法模式来统一结构。
- 框架开发: 框架通常定义处理流程的骨架,而将具体的应用逻辑交给使用者(通过继承框架类)来实现。
- Android 中的场景:
- Activity/Fragment 生命周期: 虽然不是开发者自己定义的模板方法,但 Android 框架本身就像一个巨大的模板。它定义了 Activity 的生命周期(onCreate, onStart, onResume, onPause, onStop, onDestroy 等)这个“算法骨架”,并在特定时机调用这些方法(作为钩子或必须实现的方法)。开发者通过重写这些方法来填充具体的业务逻辑,但不能改变生命周期的调用顺序。
- AsyncTask (已废弃,但作为例子很典型): AsyncTask 定义了异步任务的执行流程(模板方法 execute() 内部逻辑)。它要求子类实现 doInBackground() (抽象方法),并提供了 onPreExecute(), onPostExecute(), onProgressUpdate() 等钩子方法供子类覆盖,以在特定阶段执行 UI 更新或其他操作。
- 自定义 View: 当你继承 View 或 ViewGroup 并重写 onMeasure(), onLayout(), onDraw() 时,你就是在填充 Android 视图绘制流程(一个模板)中的特定步骤。框架会在合适的时机调用你重写的方法。
- RecyclerView.Adapter: onCreateViewHolder() 和 onBindViewHolder() 必须由子类实现,它们在 RecyclerView 的布局和滚动过程中(模板流程)被调用。
- 通用的数据处理流程: 比如定义一个基础的数据加载器 BaseDataLoader,其模板方法 loadData() 包含步骤:showLoading(), T fetchData(), processData(T), showContent(ProcessedData), handleError(Exception)。其中 fetchData 和 processData 可能是抽象的,showLoading, showContent, handleError 可以是具体或钩子方法。
UML 图
下面是基于一个“文档处理器”示例的模板方法模式 UML 图:
- AbstractDocumentProcessor (AbstractClass): 定义了模板方法 processDocument()(标记为 final 确保结构不变)。它调用了一系列抽象方法 (readFileContent, parseContent)、具体方法 (openDocument, analyzeData, saveAnalysis, closeDocument) 和一个钩子方法 (shouldSaveAnalysis)。
- TextDocumentProcessor (ConcreteClass): 继承 AbstractDocumentProcessor,实现了抽象方法 readFileContent 和 parseContent。它还覆盖了钩子方法 shouldSaveAnalysis 来决定是否保存分析结果。
- CsvDocumentProcessor (ConcreteClass): 另一个具体子类,实现了抽象方法,并且还覆盖了一个具体方法 analyzeData 来提供针对 CSV 的特定分析逻辑。它使用默认的 shouldSaveAnalysis 钩子行为。
- Client: 使用 AbstractDocumentProcessor 类型的引用来调用 processDocument(),无需关心具体是哪个子类在执行。
代码示例
在这个例子中:
- AbstractDocumentProcessor 定义了处理文档的 processDocument 模板方法,它规定了打开、读取、解析、分析、保存(可选)、关闭的顺序。
- readFileContent 和 parseContent 是抽象的,必须由 TextDocumentProcessor 和 CsvDocumentProcessor 提供具体实现。
- analyzeData 是具体的,但 CsvDocumentProcessor 选择覆盖它以提供更适合 CSV 的分析。
- shouldSaveAnalysis 是一个钩子,TextDocumentProcessor 覆盖它返回 false 来跳过保存步骤,而 CsvDocumentProcessor 使用默认的 true。
- 客户端代码只需要与 AbstractDocumentProcessor 交互,调用 processDocument() 即可执行特定类型的文档处理流程。
总结
- 模板方法模式通过在基类中定义算法骨架,并将具体步骤延迟到子类,实现了代码复用和流程控制。
- 它利用抽象方法强制子类实现变化点,利用钩子方法提供可选的扩展点。
- 在 Android 开发中,虽然我们更多地是作为框架模板方法的使用者(如重写生命周期、视图绘制方法),但理解这个模式有助于我们设计自己的可复用、结构化的处理流程(如数据加载、网络请求封装等)。
记住它的核心:固定算法骨架在父类,子类填充抽象步骤,可选覆盖钩子方法。
- Author:CoderWdd
- URL:https://www.wuinsights.top//article/f8a7a8b8-66fd-4107-afe1-19d45699e117
- Copyright:All articles in this blog, except for special statements, adopt BY-NC-SA agreement. Please indicate the source!
Relate Posts