ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

模板方法模式详解:父类定骨架、子类填细节(含可运行代码 + 钩子方法 + 与策略辨析)

模板方法模式详解:父类定骨架、子类填细节(含可运行代码 + 钩子方法 + 与策略辨析) 一、什么是模板方法模式定义GOF定义一个操作中的算法骨架而将一些步骤延迟到子类中。模板方法使得子类可以不改变算法结构即可重定义该算法的某些特定步骤。一句话理解流程先做头、再做身、最后做尾写在父类且不可被改具体每一步怎么做由子类实现。父类掌控顺序子类只管细节。二、为什么需要模板方法模式动机假设有多款车要造每款车的组装顺序都是车头 → 车身 → 车尾流程一致只是各部件的具体做法不同。不用模板方法每个MakeXxx类都各自写一遍完整make()流程代码大量重复违背 DRY。流程顺序是散落的某天要统一改成车头→车尾→车身得改 N 个类违反开闭原则、极易漏改。用模板方法把车头→车身→车尾这个不变的顺序提炼到父类的final make()里子类只实现makeHead/makeBody/makeTail的细节。流程只在一处定义加新车型只需新增一个子类。三、模板方法模式的结构角色和职责角色职责AbstractClass抽象类定义模板方法make()算法骨架通常声明为final防止子类破坏顺序声明若干抽象步骤方法子类必须实现可提供具体方法和钩子方法ConcreteClass具体子类实现父类的抽象步骤方法定义各步骤的具体细节类结构图mermaid«abstract»MakeCar#makeHead()#makeBody()#makeTail()isAlarm() : : booleanmakeAlarm()make() : finalMakeBusmakeHead()makeBody()makeTail()MakeJeepmakeHead()makeBody()makeTail()isAlarm() : : booleanmake() 用 final 锁死流程\n子类禁止重写四、代码举例修正反模式// 1. 抽象类定义骨架 publicabstractclassMakeCar{// 抽象步骤子类必须实现细节publicabstractvoidmakeHead();publicabstractvoidmakeBody();publicabstractvoidmakeTail();// 钩子方法Hook默认不装警报子类可重写来控制流程分支publicbooleanisAlarm(){returnfalse;}publicvoidmakeAlarm(){System.out.println(组装警报器);}// 模板方法算法骨架final 防止子类破坏顺序publicfinalvoidmake(){this.makeHead();this.makeBody();this.makeTail();if(isAlarm()){// 钩子控制是否执行某步this.makeAlarm();}}}// 2. 具体子类公交车不装警报 publicclassMakeBusextendsMakeCar{OverridepublicvoidmakeHead(){System.out.println(Bus组装车头);}OverridepublicvoidmakeBody(){System.out.println(Bus组装车身);}OverridepublicvoidmakeTail(){System.out.println(Bus组装车尾);}// 不重写 isAlarm() → 默认 false无警报步骤}// 2. 具体子类吉普车装警报 publicclassMakeJeepextendsMakeCar{OverridepublicvoidmakeHead(){System.out.println(Jeep组装车头);}OverridepublicvoidmakeBody(){System.out.println(Jeep组装车身);}OverridepublicvoidmakeTail(){System.out.println(Jeep组装车尾);}OverridepublicbooleanisAlarm(){returntrue;}// 钩子打开装警报}// 3. 测试 publicclassMainClass{publicstaticvoidmain(String[]args){MakeCarbusnewMakeBus();bus.make();MakeCarjeepnewMakeJeep();jeep.make();}}运行结果Bus组装车头 Bus组装车身 Bus组装车尾 Jeep组装车头 Jeep组装车身 Jeep组装车尾 Jeep组装警报器可以看到流程完全由父类make()掌控Jeep 通过重写钩子isAlarm()就让算法多走了一步而没有碰过make()本身——这就是模板方法的反向控制。五、进阶钩子方法Hook与好莱坞原则钩子方法是模板方法模式的可选步骤机制父类在骨架里留一个默认实现甚至空实现的方法调用子类按需重写来影响算法行为如上面的isAlarm()。它和抽象步骤的区别在于抽象步骤子类必须实现钩子子类可选重写。由此引出模板方法的著名设计原则——好莱坞原则Hollywood Principle“Don’t call us, we’ll call you.”别打电话给我们我们会打给你。含义子类不要主动调用父类的流程方法去驱动算法而是父类在合适的时机反向调用子类实现的细节方法。父类掌控全局子类只被动被调用。这避免了子类破坏整体流程也是框架设计的惯用套路框架调你的代码而不是你调框架的流程。六、模板方法 vs 策略模式极易混淆两者都解决了算法变化但机制完全不同维度模板方法模式策略模式复用算法的方式继承父类定骨架子类填细节组合持有一个策略对象算法结构骨架固定仅细节可变整个算法可整体替换耦合度子类与父类强耦合继承关系客户端与策略松耦合面向接口运行时切换编译期由子类决定一般不可热切换可运行时动态换算法一句话区别“我父类定流程你子类填坑”“我上下文持有一张牌随时换打法”记忆口诀流程不变用模板方法继承整盘算法要换用策略组合。七、真实世界中的模板方法JDK / 框架javax.servlet.HttpServletservice(HttpServletRequest, HttpServletResponse)是模板方法内部根据请求方法分派到doGet/doPost/doPut等这些就是子类填的步骤。你写 Servlet 从不需要重写service()只重写doGet/doPost——典型的模板方法。SpringJdbcTemplateexecute()/query()是模板方法把获取连接→创建语句→执行→异常处理→关闭资源的骨架锁死把如何处理 ResultSet通过回调RowMapper/PreparedStatementSetter交给你填。java.io.InputStreamread(byte[] b)是模板方法内部循环调用抽象的read()单字节读取。java.util.AbstractList/AbstractSetaddAll、iterator等用抽象方法get/set/size拼出骨架具体列表只实现少量抽象方法。JUnit 3TestCase.runBare()是模板方法依次调setUp()→testXxx()→tearDown()。八、应用场景与优缺点适用场景多个类有相同的操作步骤只是部分细节不同。需要固定算法流程、防止子类改乱顺序用final模板方法。希望通过钩子让子类有选择地参与流程。优点代码复用公共流程只在父类写一次。反向控制好莱坞原则父类调子类流程不被破坏。符合开闭操作侧新增子类容易不改父类骨架。缺点父类牵一发动全身骨架一改所有子类受影响。类数量增加每个变体一个子类。继承耦合子类受限于父类设计灵活性低于策略组合。九、总结一句话模板方法模式 父类用final锁死算法流程子类只实现会变的步骤必要时用钩子微调分支。记忆口诀骨架锁父类细节交子类流程别乱改差异才重写钩子控分支好莱坞反调。
返回列表