文档章节

一个最简单的设计模式-模板方法

SEOwhywhy
 SEOwhywhy
发布于 07/15 00:30
字数 1534
阅读 10
收藏 0

  我又不乱来
  
  一个最简单的设计模式-模板方法
  
  《Head First设计模式》已经读了不止一遍,但是始终没有进行系统的进行总结。所以近期开始总结设计模式相关的知识,从模板方法模式开始,因为是一个我认为是最简单的设计模式。(推荐视频资源23个设计模式)
  
  提出&解决问题
  
  提出问题
  
  实现制作咖啡功能。且制作咖啡需要四个步骤 :
  
  烧水
  
  冲泡咖啡
  
  倒入杯中
  
  加糖
  
  代码实现
  
  /**
  
  * 一杯加糖咖啡
  
  *
  
  * @author Jann Lee
  
  * @date 2019-07-14 18:37
  
  */
  
  public class Coffee {
  
  /**
  
  * 制作一杯加糖咖啡
  
  */
  
  public void prepareRecipe() {
  
  boilWater();
  
  steepTeaBag();
  
  portInCup();
  
  addLemon();
  
  }
  
  /**
  
  * step1: 烧水
  
  */
  
  private void boilWater() {
  
  System.out.println("烧水...");
  
  }
  
  /**
  
  * step2:冲泡咖啡
  
  */
  
  private void steepTeaBag() {
  
  System.out.println("冲泡咖啡...");
  
  }
  
  /**
  
  * step3: 倒入杯中
  
  */
  
  private void portInCup() {
  
  System.out.println("倒入杯中...");
  
  }
  
  /**
  
  * step4: 加糖
  
  */
  
  private void addLemon() {
  
  System.out.println("加糖...");
  
  }
  
  再次提出问题此时此刻我需要一杯柠檬茶呢?【烧水,冲泡茶包,倒入杯中,加柠檬】
  
  这个问题当然很简单,我们只需要如法炮制即可。
  
  public class Tea {
  
  /**
  
  * 制作一杯柠檬茶
  
  */
  
  public void prepareRecipe(){
  
  boilWater();
  
  brewCoffeeGrinds();
  
  portInCup();
  
  addSugarAndMilk();
  
  }
  
  /**
  
  * step1: 烧水
  
  */
  
  private void boilWater() {
  
  System.out.println("烧水...");
  
  }
  
  /**
  
  * step2:冲泡咖啡
  
  */
  
  private void brewCoffeeGrinds() {
  
  System.out.println("冲泡茶包...");
  
  }
  
  /**
  
  * step3: 倒入杯中
  
  */
  
  private void portInCup() {
  
  System.out.println("倒入杯中...");
  
  }
  
  /**
  
  * step4: 加柠檬
  
  */
  
  private void addSugarAndMilk() {
  
  System.out.println("加入柠檬片...");
  
  }
  
  }
  
  思考
  
  ​ 如果此时我们又需要一杯不加柠檬的茶,加奶的咖啡...,当然我们可以按照上面方式重新依次实现即可。但是如果你是一个有经验的程序员,或者你学习过设计模式。你可能会发现以上功能实现的步骤/流程固定,当需求发生变化时,只有小部分步骤有所改变。
  
  优化代码
  
  根据面向对象程序的特点,既抽象,封装,继承,多态。我们可以对代码进行抽象,将公共代码提取到基类。我们将咖啡和茶抽象成咖啡因饮料,将其中相同的两步,烧水和倒入杯中再父类中实现,将冲泡和添加调料延迟到子类。
  
  定义一个基类
  
  public abstract class CafeineBeverage {
  
  /**
  
  * 制作一杯咖啡因饮料
  
  */
  
  public void prepareRecipe() {
  
  boilWater();
  
  brew();
  
  portInCup();
  
  addCondiments();
  
  }
  
  /**
  
  * step1: 烧水
  
  */
  
  private void boilWater() {
  
  System.out.println("烧水...");
  
  }
  
  /**
  
  * step2:冲泡
  
  */
  
  protected abstract void brew();
  
  /**
  
  * step3: 入杯中
  
  */
  
  private void portInCup() {
  
  System.out.println("倒入杯中...");
  
  }
  
  /**
  
  * step4: 加调料
  
  */
  
  protected abstract void addCondiments();
  
  }
  
  // 一杯加糖咖啡
  
  public class CoffeeBeverage extends CafeineBeverage{
  
  @Override
  
  protected void brew() {
  
  System.out.println("冲泡咖啡...");
  
  }
  
  @Override
  
  protected void addCondiments() {
  
  System.out.println("加糖...");
  
  }
  
  }
  
  // 一杯柠檬茶
  
  public class TeaBeverage extends CafeineBeverage {
  
  @Override
  
  protected void brew() {
  
  System.out.println("冲泡茶包...");
  
  }
  
  @Override
  
  protected void addCondiments() {
  
  System.out.println("加柠檬...");
  
  }
  
  }
  
  模板方法模式
  
  如果按以上方式对代码进行了优化,其实就实现了模板方法模式。一下是模板方法模式相关概念。
  
  动机
  
  在软件构建过程中,对于某一项任务,它常常有稳定的整体操作结构,但是各个子步骤却有很多改变的需求,或者由于固有的原因(比如框架与应用之间的关系)而无法和任务的整体结构同时实现
  
  如何在确定稳定的操作结构的前提下,来灵活应对各个子步骤的变化或者晚期实现需求?
  
  定义
  
  定义一个操作中算法的骨架(稳定),而将一些步骤延迟(变化)到子类。Template Method使得子类可以不改变(复用)一个算法的结构,即可重新定义(override)该算法的特定步骤。
  
  要点总结
  
  Template Method是一种非常基础性的设计模式,在面向对象系统中,有着大量的应用。他用最简洁的机制(抽象类的多态,为很多应用框架提供了灵活的扩展点,是代码复用方面最基本实现结构)
  
  除了可以灵活应对子步骤的变化外,“不要调用我,让我来调用你”的反向控制结构是Template Method的典型应用
  
  在具体实现方面,被Template Method调用得虚方法可以有实现,也可以没有实现(抽象方法),但一般推荐设置为protected方法
  
  public class FeignClientBuilderTests {
  
  @Rule
  
  public ExpectedException thrown = ExpectedException.none();
  
  private FeignClientBuilder feignClientBuilder;
  
  private ApplicationContext applicationContext;
  
  private static Object getDefaultValueFromFeignClientAnnotation(
  
  final String methodName) {
  
  final Method method = ReflectionUtils.findMethod(FeignClient.class, methodName);
  
  return method.getDefaultValue();
  
  }
  
  private static void assertFactoryBeanField(final FeignClientBuilder.Builder builder,
  
  final String fieldName, final Object expectedValue) {
  
  final Field factoryBeanField = ReflectionUtils
  
  .findField(FeignClientBuilder.Builder.class, "feignClientFactoryBean");
  
  ReflectionUtils.makeAccessible(factoryBeanField);
  
  final FeignClientFactoryBean factoryBean = (FeignClientFactoryBean) ReflectionUtils
  
  .getField(factoryBeanField, builder);
  
  final Field field = ReflectionUtils.findField(FeignClientFactoryBean.class,
  
  fieldName);
  
  ReflectionUtils.makeAccessible(field);
  
  final Object value = ReflectionUtils.getField(field, factoryBean);
  
  assertThat(value).as("Expected value for the field '" + fieldName + "':")
  
  .isEqualTo(expectedValue);
  
  }
  
  @Before
  
  public void setUp() {
  
  this.applicationContext = Mockito.mock(ApplicationContext.class);
  
  this.feignClientBuilder = new FeignClientBuilder(this.applicationContext);
  
  }
  
  @Test
  
  public void safetyCheckForNewFieldsOnTheFeignClientAnnotation() {
  
  final List<String> methodNames = new ArrayList();
  
  for (final Method method : FeignClient.class.getMethods()) {
  
  methodNames.add(method.getName());
  
  }
  
  methodNames.removeAll(
  
  Arrays.asList("annotationType", "value", "serviceId", "qualifier",
  
  "configuration", "primary", "equals", "hashCode", "toString"));
  
  Collections.sort(methodNames);
  
  // If this safety check fails the Builder has to be updated.
  
  // (1) Either a field was removed from the FeignClient annotation and so it has to
  
  // be removed
  
  // on this builder class.
  
  // (2) Or a new field was added and the builder class has to be extended with this
  
  // new field.
  
  assertThat(methodNames).containsExactly("contextId", "decode404", "fallback",
  
  "fallbackFactory", "name", "path", "url");
  
  }
  
  @Test
  
  public void forType_preinitializedBuilder(www.xingyunylpt.com) {
  
  // when:
  
  final FeignClientBuilder.Builder builder = this.feignClientBuilder
  
  .forType(FeignClientBuilderTests.class, "TestClient");
  
  // then:
  
  assertFactoryBeanField(builder, "applicationContext", this.applicationContext);
  
  assertFactoryBeanField(builder, "type", FeignClientBuilderTests.class);
  
  assertFactoryBeanField(builder, "name", "TestClient");
  
  assertFactoryBeanField(builder, www.zbyL2019.com"contextId", "TestClient");
  
  // and:
  
  assertFactoryBeanField(builder, "url",
  
  getDefaultValueFromFeignClientAnnotation(www.chengsyl.cn"url"));
  
  assertFactoryBeanField(builder, "path",
  
  getDefaultValueFromFeignClientAnnotation(www.dfpigt.com"path"));
  
  assertFactoryBeanField(builder, "decode404",
  
  getDefaultValueFromFeignClientAnnotation("decode404"));
  
  assertFactoryBeanField(builder, "fallback",
  
  getDefaultValueFromFeignClientAnnotation("fallback"));
  
  assertFactoryBeanField(builder, "fallbackFactory",
  
  getDefaultValueFromFeignClientAnnotation("fallbackFactory"));
  
  }
  
  @Test
  
  public void forType_allFieldsSetOnBuilder() {
  
  // when:
  
  final FeignClientBuilder.Builder builder = this.feignClientBuilder
  
  .forType(FeignClientBuilderTests.class, "TestClient").decode404(true)
  
  .fallback(Object.class).fallbackFactory(Object.class).path("Path/")
  
  .url("Url/");
  
  // then:
  
  assertFactoryBeanField(builder, "applicationContext", this.applicationContext);
  
  assertFactoryBeanField(builder, "type", FeignClientBuilderTests.class);
  
  assertFactoryBeanField(builder, "name", "TestClient");
  
  // and:
  
  assertFactoryBeanField(builder, "url", www.qunfLtie.com"http://Url/");
  
  assertFactoryBeanField(builder, "path", "/Path");
  
  assertFactoryBeanField(builder, "decode404", true);
  
  assertFactoryBeanField(builder,www.chuangyyuLe.com "fallback", Object.class);
  
  assertFactoryBeanField(builder, "fallbackFactory", Object.class);
  
  }
  
  @Test
  
  public void forType_build() {
  
  // given:
  
  Mockito.when(this.applicationContext.getBean(FeignContext.class))
  
  .thenThrow(new ClosedFileSystemException()); // throw an unusual exception
  
  // in the
  
  // FeignClientFactoryBean
  
  final FeignClientBuilder.Builder builder = this.feignClientBuilder
  
  .forType(TestClient.class, "TestClient");
  
  // expect: 'the build will fail right after calling build() with the mocked
  
  // unusual exception'
  
  this.thrown.expect(Matchers.isA(ClosedFileSystemException.class));
  
  builder.build();
  
  }
  
  }
  
  FeignClientBuilderTests验证了safetyCheckForNewFieldsOnTheFeignClientAnnotation、forType_preinitializedBuilder、forType_allFieldsSetOnBuilder、forType_build
  
  小结
  
  FeignClientBuilder提供了forType静态方法用于创建Builder;Builder的构造器创建了FeignClientFactoryBean,其build方法使用FeignClientFactoryBean的getTarget()来创建目标feign client

© 著作权归作者所有

SEOwhywhy
粉丝 8
博文 152
码字总数 335019
作品 0
私信 提问
设计模式15——Template Method设计模式

Template Method模板方法设计模式定义一个操作中算法的骨架,将具体步骤的执行延迟到子类中实现。Java中的抽象类就是使用了模板方法设计模式。模板方法设计模式结构如下: 以文档处理为例,T...

小米米儿小
2014/01/24
201
0
EasyToLearnDesignPattern

简单上手设计模式 GITHUB:https://github.com/Fisher-Joe/EasyToLearnDesignPattern 本文旨在使用最简单的语言,最简单的代码让人学习设计模式(最起码是我) 说明: 本文的所有内容都是基于...

芝麻开门
04/19
0
0
【设计模式笔记】(十六)- 代理模式

一、简述 代理模式(Proxy Pattern),为其他对象提供一个代理,并由代理对象控制原有对象的引用;也称为委托模式。 其实代理模式无论是在日常开发还是设计模式中,基本随处可见,中介者模式中...

MrTrying
2018/06/24
0
0
设计模式Java Design Pattern-工厂方法模式FactoryMethod

我的博客 一、 设计模式的分类 大体可以分为三类: 创建型模式(5个) 单例模式、原型模式、工厂方法模式、抽象工厂模式、建造者模式 结构性模式(7个) 适配器模式、装饰器模式、代理模式、...

勇敢写信
2018/03/22
0
0
JavaScript设计模式总结

之前看过《JavaScript设计模式与开发实践》这本书,对书中的设计模式和一些相关案例也有了一定的了解,同时把这些设计模式的应用对应在在一些其他的项目中,进行了一些整理,如下仅供参考: ...

jefferyE
03/26
0
0

没有更多内容

加载失败,请刷新页面

加载更多

刚哥谈架构 (二) 我眼中的架构师

之前在公司,有小伙伴在向别人介绍我的时候,经常会有人这么说:“刚哥是我们的architcture”,如果来人是老外,心中一定是一惊,心中暗叹,“这位匪首看上去貌不惊人,难道已经做到了架构和...

naughty
54分钟前
5
0
OSChina 周日乱弹 —— 别问,问就是没空

Osc乱弹歌单(2019)请戳(这里) 【今日歌曲】 @tom_tdhzz :#今日歌曲推荐# 分享容祖儿/彭羚的单曲《心淡》: 《心淡》- 容祖儿/彭羚 手机党少年们想听歌,请使劲儿戳(这里) @wqp0010 :周...

小小编辑
今天
144
4
golang微服务框架go-micro 入门笔记2.1 micro工具之micro api

micro api micro 功能非常强大,本文将详细阐述micro api 命令行的功能 重要的事情说3次 本文全部代码https://idea.techidea8.com/open/idea.shtml?id=6 本文全部代码https://idea.techidea8....

非正式解决方案
今天
5
0
Spring Context 你真的懂了吗

今天介绍一下大家常见的一个单词 context 应该怎么去理解,正确的理解它有助于我们学习 spring 以及计算机系统中的其他知识。 1. context 是什么 我们经常在编程中见到 context 这个单词,当...

Java知其所以然
昨天
5
0
Spring Boot + Mybatis-Plus 集成与使用(二)

前言: 本章节介绍MyBatis-Puls的CRUD使用。在开始之前,先简单讲解下上章节关于Spring Boot是如何自动配置MyBatis-Plus。 一、自动配置 当Spring Boot应用从主方法main()启动后,首先加载S...

伴学编程
昨天
8
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部