浅谈设计模式 - 模板方法(十)

本文涉及的产品
Redis 开源版,标准版 2GB
推荐场景:
搭建游戏排行榜
云数据库 Tair(兼容Redis),内存型 2GB
简介: 模板方法模式在JAVA当中最为熟知的就是spring的template对象,模板方法和策略这两个模式需要小心的区分,关于模板方法模式只需要重点记忆一句话:模板方法的模式定义了算法的骨架。同时针对模板方法的的一项设计原则好莱坞原则也是对 依赖倒转原则一种很好的补充和扩展。

前言:


模板方法模式在JAVA当中最为熟知的就是springtemplate对象,模板方法和策略这两个模式需要小心的区分,关于模板方法模式只需要重点记忆一句话:模板方法的模式定义了算法的骨架。同时针对模板方法的的一项设计原则好莱坞原则也是对 依赖倒转原则一种很好的补充和扩展。


文章目的:


  1. 了解模板方法,同时了解模板方法是如何体现好莱坞原则的。
  2. 模板方法与策略模式的对比,以及模板方法灵活运用钩子函数。
  3. 模板方法的简单案例,以及在spring框架当中的具体体现。


什么是模板方法


基本定义


定义:在一个方法当中定义了算法的骨架,而将具体的实现延迟到子类当中。模板方法在不改变算法结构的情况下,重构算法的某些步骤。


从现实看模板方法


我们都知道现代各式各样的蛋糕都是使用模具做成的,而同一个形状的蛋糕却可以使用不同的配料,此时模具便是模板方法的骨架,通过定义具体的配料细节对应了“算法”的细节。


钩子函数


钩子函数是一种编程上比较常用的技巧,在框架设计当中十分常见,什么是钩子呢?从个人的理解来看,钩子像是可以延迟定义的匿名函数,钩子可以“勾”住某个算法的中间过程,让外部环境可以干涉内部算法实现的同时,又能让内部的函数进行自由控制钩子的使用。


钩子函数一般实现方式为抽象类或者不做任何动作的函数。


钩子函数在脚本语言里面经常被用作回调函数。包括java的许多框架也用钩子让用户可以干涉一些算法的细节。但是需要注意的是,钩子这个东西很容易破坏代码的可阅读性,所以不建议经常使用这种函数,可以用组合以及其他的设计模式对于结构进行优化。


模板方法的结构图


下面是模板方法的结构图,模板方法对比其他设计模式应该算是最简单的一个结构图了,比较容易理解:


模板方法的模式定义了算法的骨架,那么什么是定义算法的骨架,从下面的图表很好的看到,父类定义为抽象类,定义模板的算法方法和抽象的算法细节。


这里要实现算法需要由子类实现具体的算法业务


image.png


模板方法的优缺点


优点:


  • 模板方法可以让算法的细节掩盖在子类,同时抽取公共的算法,提高代码复用程度
  • 模板方法可以让修改控制在子类,而父类方法不需要进行改动,符合开放关闭原则。


缺点:


  • 模板方法类的改动对于所有的算法实现子类都会产生影响,同时模板父类改动违背“开放-关闭”原则
  • 模板方法由于利用钩子控制父类方法,会导致反向控制代码,对于代码的阅读不是十分友好。


模板方法与好莱坞原则


什么是好莱坞原则?


首先需要了解一下什么是好莱坞原则:**让我们调用你们,而不是让你们调用我**。


和依赖倒转原则有什么关联?


好莱坞原则更像是对于依赖倒转的一种扩展技巧。依赖倒转更加关注的是如何在设计中避免面向实现编程,而好莱坞则是将实现的调用在低层的结构进行隐藏。


为什么不建议低层组件调用高层组件?


为了防止环形依赖,在高层组件里面调用了抽象方法,而抽象方法又调用高层组件的方法。


策略模式和模板方法对比


策略模式和模板方法模式的对比


  1. 策略是定义一整个算法,使用组合的形式实现不同的对象切换
  2. 模板方法的是定义一个超类,在超类中通过高层调用底层实现的具体方法的实现,来实现方法的延迟功能


案例


这次的案例以个人小时候做过的一件事情举例,以前外婆兼职从厂里拿来一堆玩具零件的成品,而工作就是把成品进行“反转”(就是把做好的玩具翻面),还非常清楚的记得大概是一分钱一个,靠着帮忙那时候还拿了一些零花钱,每天放学做完作业之后就是帮外婆做“兼职”。这种重复性劳动,在代码的构建很容易想到模板方法的模式,由于各种玩具的形状不同,所以翻面的方式以及效率和速度都不同,我们将重复劳动的部分定义为顶层的模板,而具体的玩具构建细节,需要根据不同的玩具进行不同的操作,下面定义这个工作的大致流程:


image.png


下面是根据结构图绘制一个基本的代码:


// 玩具制造模板类
public abstract class TemplateWorkFlow {
    public void productToy(){
        takeToy();
        reverseToy();
        putBasket();
    }
    public final void putBasket() {
        System.out.println("把玩具放到玩具篮");
    }
    public void takeToy(){
        System.out.println("拿起玩具");
    }
    public abstract void reverseToy();
}
public class AntlersToyWorkFlow extends TemplateWorkFlow {
    @Override
    public void reverseToy() {
        System.out.println("把主干翻面");
        System.out.println("把鹿角的分叉翻页");
    }
}
public class ChristmasHatWorkFlow extends TemplateWorkFlow{
    @Override
    public void reverseToy() {
        System.out.println("圣诞帽反转");
        System.out.println("圣诞帽帽子顶部的小秋顶出去");
    }
}
public class Main {
    public static void main(String[] args) {
        TemplateWorkFlow templateWorkFlow = new ChristmasHatWorkFlow();
        TemplateWorkFlow templateWorkFlow1 = new AntlersToyWorkFlow();
        templateWorkFlow.productToy();
        templateWorkFlow1.productToy();
    }/*
    拿起玩具
    圣诞帽反转
    圣诞帽帽子顶部的小秋顶出去
    把玩具放到玩具篮
    拿起玩具
    把主干翻面
    把鹿角的分叉翻页
    把玩具放到玩具篮
    */
}


如果不使用设计模式,他大致的设计代码如下,可以看到很多方法都干了相似的事情,这些方法可能本质上只是一两行代码甚至只是取名不一样,当然现代的编译器都很“聪明”,会发现重复的点,所以最最基本的要求,是编写出编译器都无法发现的重复代码,当然仅仅凭借这一点显然要求有点低


下面看下不使用模板方法的代码:


public class ChristmasHatWorkFlow{
    public void productToy(){
        takeToy();
        reverseToy();
        putBasket();
    }
    public final void putBasket() {
        System.out.println("把玩具放到玩具篮");
    }
    public void takeToy(){
        System.out.println("拿起玩具");
    }
    public void reverseToy() {
        System.out.println("圣诞帽反转");
        System.out.println("圣诞帽帽子顶部的小秋顶出去");
    }
}
public class AntlersToyWorkFlow {
    public void productToy(){
        takeToy();
        reverseToy();
        putBasket();
    }
    public final void putBasket() {
        System.out.println("把玩具放到玩具篮");
    }
    public void takeToy(){
        System.out.println("拿起玩具");
    }
    public void reverseToy() {
        System.out.println("把主干翻面");
        System.out.println("把鹿角的分叉翻页");
    }
}
public class Main {
    public static void main(String[] args) {
        AntlersToyWorkFlow antlersToyWorkFlow = new AntlersToyWorkFlow();
        antlersToyWorkFlow.productToy();
        ChristmasHatWorkFlow christmasHatWorkFlow = new ChristmasHatWorkFlow();
        christmasHatWorkFlow.productToy();
    }/*
    拿起玩具
    把主干翻面
    把鹿角的分叉翻页
    把玩具放到玩具篮
    拿起玩具
    圣诞帽反转
    圣诞帽帽子顶部的小秋顶出去
    把玩具放到玩具篮
    */
}


spring当中的模板方法


spring最为典型的案例便是Tempalte框架,但是需要注意spring多数情况下并没有使用经典的模板方法结构,而是使用了CallBack函数的形式,避开了继承结构的同时,每个类可以单独实现自己的具体功能:


我们看一下RedisTempalte当中的StringRedisTemplate,这里调用父类的afterPropertiesSet()


public class StringRedisTemplate extends RedisTemplate<String, String> {
   /**
    * Constructs a new <code>StringRedisTemplate</code> instance ready to be used.
    *
    * @param connectionFactory connection factory for creating new connections
    */
   public StringRedisTemplate(RedisConnectionFactory connectionFactory) {
      this();
      setConnectionFactory(connectionFactory);
      afterPropertiesSet();
   }
   protected RedisConnection preProcessConnection(RedisConnection connection, boolean existingConnection) {
      return new DefaultStringRedisConnection(connection);
   }
}


父类同样继承的手段,在如下方法当中调用了afterProperteisSet(),通过super引用父类的方法:


@Override
public void afterPropertiesSet() {
  // 注意
   super.afterPropertiesSet();
   boolean defaultUsed = false;
   if (defaultSerializer == null) {
      defaultSerializer = new JdkSerializationRedisSerializer(
            classLoader != null ? classLoader : this.getClass().getClassLoader());
   }
   if (enableDefaultSerializer) {
      if (keySerializer == null) {
         keySerializer = defaultSerializer;
         defaultUsed = true;
      }
      if (valueSerializer == null) {
         valueSerializer = defaultSerializer;
         defaultUsed = true;
      }
      if (hashKeySerializer == null) {
         hashKeySerializer = defaultSerializer;
         defaultUsed = true;
      }
      if (hashValueSerializer == null) {
         hashValueSerializer = defaultSerializer;
         defaultUsed = true;
      }
   }
   if (enableDefaultSerializer && defaultUsed) {
      Assert.notNull(defaultSerializer, "default serializer null and not all serializers initialized");
   }
   if (scriptExecutor == null) {
      this.scriptExecutor = new DefaultScriptExecutor<>(this);
   }
   initialized = true;
}


下面引用父类RedisAccesor类当中的afterPropertiesSet()


public void afterPropertiesSet() {
   Assert.state(getConnectionFactory() != null, "RedisConnectionFactory is required");
}


通过定义getConnectionFactory()方法,子类可以自由的配置连接工厂,也可以直接沿用父类的默认实现。


总结


模板方法是一个比较重要的设计模式,他可以从结构上帮助程序员构建一个良好的抽象概念,同时模板方法提供的钩子函数,通过定义抽象方法延迟到子类实现这一技巧非常符合“开放-关闭”原则,灵活运用模板方法模式有利于构建更加灵活的软件骨架,同时可以定义各种多变的算法体系。但是需要注意的是传统的模板方法这种继承的结构 并不推崇,因为我们都知道继承对于所有子类都会产生影响。


另外模板方法这个模式对于阅读代码的体验不是很好,经常需要各个类之间不断切换,有时候甚至会莫名其妙为什么突然跑到另一个方法里面,模板方法有时候比较影响阅读体验。


另外设计模式最大的目的就是 减少重复代码 以及 用最小的代价进行扩展,个人认为如果代码符合这两个点基本就是一个好代码,然而这个点确实日常工作最难实现的。

相关实践学习
基于Redis实现在线游戏积分排行榜
本场景将介绍如何基于Redis数据库实现在线游戏中的游戏玩家积分排行榜功能。
云数据库 Redis 版使用教程
云数据库Redis版是兼容Redis协议标准的、提供持久化的内存数据库服务,基于高可靠双机热备架构及可无缝扩展的集群架构,满足高读写性能场景及容量需弹性变配的业务需求。 产品详情:https://www.aliyun.com/product/kvstore &nbsp; &nbsp; ------------------------------------------------------------------------- 阿里云数据库体验:数据库上云实战 开发者云会免费提供一台带自建MySQL的源数据库&nbsp;ECS 实例和一台目标数据库&nbsp;RDS实例。跟着指引,您可以一步步实现将ECS自建数据库迁移到目标数据库RDS。 点击下方链接,领取免费ECS&amp;RDS资源,30分钟完成数据库上云实战!https://developer.aliyun.com/adc/scenario/51eefbd1894e42f6bb9acacadd3f9121?spm=a2c6h.13788135.J_3257954370.9.4ba85f24utseFl
相关文章
|
6月前
|
设计模式 算法 Java
模板方法--设计模式
模板方法--设计模式
49 0
|
6月前
|
设计模式 算法 Java
模板方法设计模式(TemplateMethod)
模板方法设计模式(TemplateMethod)
|
设计模式 算法 Java
设计模式系列教程(08) - 模板方法
设计模式系列教程(08) - 模板方法
46 0
|
6月前
|
设计模式 算法 Java
Java一分钟之-设计模式:策略模式与模板方法
【5月更文挑战第17天】本文介绍了策略模式和模板方法模式,两种行为设计模式用于处理算法变化和代码复用。策略模式封装不同算法,允许客户独立于具体策略进行选择,但需注意选择复杂度和过度设计。模板方法模式定义算法骨架,延迟部分步骤给子类实现,但过度抽象或滥用继承可能导致问题。代码示例展示了两种模式的应用。根据场景选择合适模式,以保持代码清晰和可维护。
117 1
|
5月前
|
设计模式 算法
模板方法-大话设计模式
模板方法-大话设计模式
|
5月前
|
设计模式 存储 Java
JavaSE——面向对象高级二(2/4)-final关键字、常量、抽象类(认识抽象类、抽象类的好处、应用场景-模板方法设计模式)
JavaSE——面向对象高级二(2/4)-final关键字、常量、抽象类(认识抽象类、抽象类的好处、应用场景-模板方法设计模式)
28 0
|
设计模式 SQL 数据库
淘东电商项目(61) -聚合支付(基于模板方法设计模式管理支付回调)
淘东电商项目(61) -聚合支付(基于模板方法设计模式管理支付回调)
82 0
|
6月前
|
设计模式 算法 Java
【设计模式】springboot3项目整合模板方法深入理解设计模式之模板方法(Template Method)
【设计模式】springboot3项目整合模板方法深入理解设计模式之模板方法(Template Method)
|
6月前
|
设计模式 算法
设计模式之模板方法
设计模式之模板方法
|
设计模式 SQL 数据库
淘东电商项目(62) -聚合支付(基于模板方法设计模式管理支付回调-支付宝)
淘东电商项目(62) -聚合支付(基于模板方法设计模式管理支付回调-支付宝)
66 0

热门文章

最新文章

  • 1
    C++一分钟之-设计模式:工厂模式与抽象工厂
    43
  • 2
    《手把手教你》系列基础篇(九十四)-java+ selenium自动化测试-框架设计基础-POM设计模式实现-下篇(详解教程)
    50
  • 3
    C++一分钟之-C++中的设计模式:单例模式
    58
  • 4
    《手把手教你》系列基础篇(九十三)-java+ selenium自动化测试-框架设计基础-POM设计模式实现-上篇(详解教程)
    38
  • 5
    《手把手教你》系列基础篇(九十二)-java+ selenium自动化测试-框架设计基础-POM设计模式简介(详解教程)
    63
  • 6
    Java面试题:结合设计模式与并发工具包实现高效缓存;多线程与内存管理优化实践;并发框架与设计模式在复杂系统中的应用
    58
  • 7
    Java面试题:设计模式在并发编程中的创新应用,Java内存管理与多线程工具类的综合应用,Java并发工具包与并发框架的创新应用
    42
  • 8
    Java面试题:如何使用设计模式优化多线程环境下的资源管理?Java内存模型与并发工具类的协同工作,描述ForkJoinPool的工作机制,并解释其在并行计算中的优势。如何根据任务特性调整线程池参数
    50
  • 9
    Java面试题:请列举三种常用的设计模式,并分别给出在Java中的应用场景?请分析Java内存管理中的主要问题,并提出相应的优化策略?请简述Java多线程编程中的常见问题,并给出解决方案
    110
  • 10
    Java面试题:设计模式如单例模式、工厂模式、观察者模式等在多线程环境下线程安全问题,Java内存模型定义了线程如何与内存交互,包括原子性、可见性、有序性,并发框架提供了更高层次的并发任务处理能力
    78
  • 下一篇
    无影云桌面