我又不乱来
一个最简单的设计模式-模板方法
《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