一個最簡單的設計模式-模板方法

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

相關文章
相關標籤/搜索