我又不亂來
一個最簡單的設計模式-模板方法
《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程序員