一文读懂模板方法(钩子方法)

设计二十三式之模板方法

意图

模板方法是一种行为设计模式,它在超类中定义算法的骨架,但让子类在不改变其结构的情况下覆盖算法的特定步骤。

模板方法

02 问题

想象一下,您正在创建一个分析公司文档的数据挖掘应用程序。用户以各种格式(PDF、DOC、CSV)提供应用程序文档,并尝试以统一格式从这些文档中提取有意义的数据。

该应用程序的第一个版本只能使用 DOC 文件。在以下版本中,它能够支持 CSV 文件。一个月后,你“教”了它从 PDF 文件中提取数据。

文件处理器

在某些时候,您注意到所有三个类都有很多相似的代码。虽然所有类中处理各种数据格式的代码完全不同,但数据处理和分析的代码几乎相同。去掉代码重复,保持算法结构完好,不是很好吗?

还有另一个与使用这些类的客户端代码相关的问题。它有很多条件,可以根据处理对象的类别选择适当的操作过程。如果所有三个处理类都有一个公共接口或一个基类,那么您就可以消除客户端代码中的条件,并在调用处理对象上的方法时使用多态性。

03 解决方案

模板方法模式建议您将算法分解为一系列步骤,将这些步骤转化为方法,并将对这些方法的一系列调用放入单个模板方法中。这些步骤可能是abstract,或者有一些默认实现。要使用该算法,客户端应该提供自己的子类,实现所有抽象步骤,并在需要时覆盖一些可选步骤(但不是模板方法本身)。

让我们看看这将如何在我们的数据挖掘应用程序中发挥作用。我们可以为所有三种解析算法创建一个基类。此类定义了一个模板方法,该方法由对各种文档处理步骤的一系列调用组成。

简单类图

首先,我们可以声明所有步骤abstract,强制子类为这些方法提供自己的实现。在我们的例子中,子类已经有了所有必要的实现,所以我们唯一需要做的就是调整方法的签名以匹配超类的方法。

现在,让我们看看我们可以做些什么来摆脱重复的代码。看起来打开/关闭文件和提取/解析数据的代码对于各种数据格式是不同的,所以没有必要接触这些方法。但是,其他步骤的实现,例如分析原始数据和编写报告,非常相似,因此可以将其拉到基类中,子类可以共享该代码。

如您所见,我们有两种类型的步骤:

  • 每个子类都必须实现抽象步骤

  • 可选步骤已经有一些默认实现,但如果需要仍然可以覆盖

还有另一种类型的步骤,称为hooks**。钩子是具有空主体的可选步骤。即使没有覆盖挂钩,模板方法也可以工作。通常,钩子放置在算法的关键步骤之前和之后,为子类提供算法的额外扩展点。

04 举个栗子

建筑模板

模板法方法可用于大规模住房建设。建造标准房屋的建筑计划可能包含几个扩展点,可以让潜在业主调整最终房屋的一些细节。

每一个建筑步骤,例如打地基、框架、砌墙、安装水管和水电布线等,都可以稍微改变一下,使最终的房子与其他房子有点不同。

05 结构实现

在此示例中,模板方法模式为简单的策略视频游戏中的各种人工智能分支提供了“骨架”。

类图

游戏中的所有种族都有几乎相同类型的单位和建筑。因此,您可以为各种种族重用相同的 AI 结构,同时能够覆盖一些细节。使用这种方法,您可以覆盖兽人的 AI 使其更具攻击性,使人类更注重防御,并使怪物无法建造任何东西。向游戏中添加新种族需要创建一个新的 AI 子类并覆盖在基础 AI 类中声明的默认方法。


AI

Show u the code:

// The abstract class defines a template method that contains a
// skeleton of some algorithm composed of calls, usually to
// abstract primitive operations. Concrete subclasses implement
// these operations, but leave the template method itself
// intact.
class GameAI is
    // The template method defines the skeleton of an algorithm.
    method turn() is
        collectResources()
        buildStructures()
        buildUnits()
        attack()

    // Some of the steps may be implemented right in a base
    // class.
    method collectResources() is
        foreach (s in this.builtStructures) do
            s.collect()

    // And some of them may be defined as abstract.
    abstract method buildStructures()
    abstract method buildUnits()

    // A class can have several template methods.
    method attack() is
        enemy = closestEnemy()
        if (enemy == null)
            sendScouts(map.center)
        else
            sendWarriors(enemy.position)

    abstract method sendScouts(position)
    abstract method sendWarriors(position)

// Concrete classes have to implement all abstract operations of
// the base class but they must not override the template method
// itself.
class OrcsAI extends GameAI is
    method buildStructures() is
        if (there are some resources) then
            // Build farms, then barracks, then stronghold.

    method buildUnits() is
        if (there are plenty of resources) then
            if (there are no scouts)
                // Build peon, add it to scouts group.
            else
                // Build grunt, add it to warriors group.

    // ...

    method sendScouts(position) is
        if (scouts.length > 0) then
            // Send scouts to position.

    method sendWarriors(position) is
        if (warriors.length > 5) then
            // Send warriors to position.

// Subclasses can also override some operations with a default
// implementation.
class MonstersAI extends GameAI is
    method collectResources() is
        // Monsters don't collect resources.

    method buildStructures() is
        // Monsters don't build structures.

    method buildUnits() is
        // Monsters don't build units.

06 适用场景

  • 当您想让客户端仅扩展算法的特定步骤而不是整个算法或其结构时,请使用模板方法模式。

  • 模板方法允许您将单一算法转换为一系列单独的步骤,这些步骤可以很容易地被子类扩展,同时保持超类中定义的结构完整。

  • 当您有几个类包含几乎相同的算法但有一些细微差别时,请使用该模式。因此,您可能需要在算法更改时修改所有类。

  • 当你把这样的算法变成模板方法时,你也可以把实现类似的步骤拉到一个超类中,从而消除代码重复。子类之间变化的代码可以保留在子类中。

07 如何实施

  1. 分析目标算法,看看是否可以将其分解为步骤。考虑哪些步骤对所有子类都是通用的,哪些步骤总是唯一的。

  2. 创建抽象基类并声明模板方法和一组表示算法步骤的抽象方法。通过执行相应的步骤,在模板方法中概述算法的结构。考虑制作模板方法final以防止子类覆盖它。

  3. 如果所有步骤最终都是抽象的,那也没关系。但是,某些步骤可能会受益于默认实现。子类不必实现这些方法。

  4. 考虑在算法的关键步骤之间添加挂钩。

  5. 对于算法的每个变体,创建一个新的具体子类。它必须实现所有抽象步骤,但也可能覆盖一些可选步骤。

08 优缺点

image

欢迎阅读公众号内容:https://mp.weixin.qq.com/s?__biz=Mzk0NjI5NzE1Ng==&mid=2247483711&idx=1&sn=9a15b9ced32370596f09eebd4b09124f&chksm=c3090114f47e8802b73453ebe1aa68e691826837cc08e8eec3e18cc7c69043acf38cf66e7c64&scene=0&subscene=10000&clicktime=1641862169&enterid=1641862169&ascene=7&devicetype=android-30&version=2800105d&nettype=cmnet&abtest_cookie=AAACAA%3D%3D&lang=zh_CN&exportkey=A9AxVzw7XfyOfR15j7HEQ%2F4%3D&pass_ticket=43I3jBpkl1S5UpV06lsIqbZPk96G4oIgbi1e%2FdZbLUAW%2Fr0xE9pVGJMIbWeb6xoQ&wx_header=1

关注公众号可获取干货资料:《设计模式详解》《JAVA基础知识总结》 《java面试手册》等


image.png

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

推荐阅读更多精彩内容