建造者模式及應用舉例


模式名和分類

builder
創建型模式

意圖

將一個復雜對象的構建與它的表示分離,使得同樣的構建過程可以創建不同的表示。

將對象的創建過程細化並固化,能依此創建一個流水線,在流水線上組裝對象的各個零件,最終生成我們想要的對象

動機

spring創建對象時,我們在解析xml文件和一些系統配置來創建一個的BeanDefinition,解析過程是復雜且多樣的。
如果准備好所有的參數,並在new的時候直接賦值進去,難免會讓創建對象這個動作顯得臃腫且難看。
我們的想法是解析一些元素,處理一些元素,就將他設置到“待創建的對象中”,就像流水線一樣,做好一個零件就安裝一個零件,知道最后流水線走完,所有零件都到位,就能得到一個完整的對象。
總之:由於bean的復雜性,我們想細化這個過程,更完美的控制它的創建。

適用性

  • 如果你現在創建對象需要傳入很多參數,又不想在構造函數中傳入太多參數,使得代碼難以閱讀。
  • 如果你創建的對象非常的復雜,里面的很多細節或者屬性需要不斷的處理,而你想處理完一個組件就能毫無顧忌的處理下一個,而不用擔心會忘記或者混亂已經處理好的模塊。

結構

參與者

  • ProductBuilder:產品建造師。它負責創建產品
  • Product:聲明父類,表明有多種產品,每種產品都有partA和partB,但是不同的產品的partA和partB有不同的實現細節
  • ProductXX、ProductYY:產品XX和YY,擁有自己的partA和partB實現方式。

協作

  • builder中聚合了Product。在構造函數中傳入具體產品,例如要創建ProductXX,在構造函數中傳入ProductXX.class(只要能確定具體產品,什么形式都行),表明要創建ProductXX。
  • 創建partA,已經知道了我們要創建ProductXX,那么setPartA,就設置屬於ProductXX的partA。
  • 創建partB,已經知道了我們要創建ProductXX,那么setPartB,就設置屬於ProductXX的partB。

效果

  • 我們支持創建不同的產品。
  • 我們可以根據需要為每個產品設置不同的值,或者不同的操作。
  • 我們可以一一設置產品的屬性,而不需要在構造函數中統一傳入,如果屬性很多,那豈不是在構造函數中要傳入一大串參數....看到看不了。

代碼實例

例1、模式代碼

// 步驟1 創建產品簇
public abstract class AbstractProduct {
    protected String name;
    protected String partA;
    protected String partB;

    AbstractProduct(String name){
        this.name = name;
    }

    public String getPartA() {
        return partA;
    }

    public void setPartA(String partA) {
        this.partA = partA;
    }
    public void setPartB(String partB) {
        this.partB = partB;
    }

    public String toString(){
        return "name:"+name+", partA:"+partA+", partB:"+partB;
    }
}

public class ProductXX extends AbstractProduct{
    public ProductXX(String name){
        super(name);
    }
}

public class ProductYY extends AbstractProduct{
    public ProductYY(String name){
        super(name);
    }
}


// 步驟2 創建建造者,建造師...
// 他來創建產品的細節...
public class ProductBuilder {
    private final AbstractProduct product;

    public ProductBuilder(AbstractProduct product) {
        this.product = product;
    }

    // 設置partA
    public AbstractProduct setPartA(String partA){
        this.product.setPartA(partA);
        return this.product;
    }

    // 設置partB
    public AbstractProduct setPartB(String partB){
        this.product.setPartB(partB);
        return this.product;
    }

    public AbstractProduct getProduct(){
        this.check();
        return product;
    }

    /**
     * 這個方法用來校驗product的關鍵信息是否全部設置完成。
     * @param
     * @author caodahuan
     * @date 2019/8/21
     * @return void
     */
    private void check() {
        // todo
    }
}

// 步驟3,重點!如何使用建造者幫我們建造
public class Director {
    private ProductBuilder builder;

    public Director(ProductBuilder builder) {
        this.builder = builder;
    }

    public AbstractProduct getProduct(){
        builder.setPartA("零件A");
        builder.setPartB("零件B");
        return builder.getProduct();
    }
}

// 測試
public class TestMain {
    public static void main(String[] args) {
        Director director = new Director(new ProductBuilder(new ProductXX("產品XX")));
        AbstractProduct product = director.getProduct();
        System.out.println(product.toString());

        director = new Director(new ProductBuilder(new ProductXX("產品YY")));
        product = director.getProduct();
        System.out.println(product.toString());
    }
}

已知應用

  • 工廠流水線
  • spring中對於建造者的使用

spring框架,主要是用來管理對象,創建一個對象是極其復雜的,建造者模式在解析xml文件,創建BeanDefiniton中發揮很大的作用。不想在這里做更細節的分析,但是可以找段代碼,分析spring是如何使用它

  • 段落1:spring-security中解析xml
private BeanReference registerMethodSecurityInterceptor(ParserContext pc,
      String authMgrRef, String accessManagerId, String runAsManagerId,
      BeanReference metadataSource,
      List<BeanMetadataElement> afterInvocationProviders, Object source,
      boolean useAspectJ) {
    // 建造者:建造方法攔截器beanDefinition,如果開啟切面,則使用AspectJMethodSecurityInterceptor,若沒有開啟切面,則使用MethodSecurityInterceptor
   BeanDefinitionBuilder bldr = BeanDefinitionBuilder
         .rootBeanDefinition(useAspectJ ? AspectJMethodSecurityInterceptor.class
               : MethodSecurityInterceptor.class);
    // 建造者:添加資源
   bldr.getRawBeanDefinition().setSource(source);
    // 建造者:添加“決策(放行)管理器”
   bldr.addPropertyReference("accessDecisionManager", accessManagerId);
    // 建造者:定義“鑒權管理器”BeanDefinition
   RootBeanDefinition authMgr = new RootBeanDefinition(
         AuthenticationManagerDelegator.class);
    // 建造者:將自定義“鑒權管理實現”注入到鑒權管理BeanDifinition
   authMgr.getConstructorArgumentValues().addGenericArgumentValue(authMgrRef);
   bldr.addPropertyValue("authenticationManager", authMgr);
    // 安全相關數據源BeanReference
   bldr.addPropertyValue("securityMetadataSource", metadataSource);

    // 建造者:如果擁有賦權,也將添加到beanDefinition中
   if (StringUtils.hasText(runAsManagerId)) {
      bldr.addPropertyReference("runAsManager", runAsManagerId);
   }

    // 如果自定義了處理器,也要為處理器制作一個BeanDefinition,這里使用RootBeanDefinition是在2.5以前的做法,表示作為根BeanDefinition.
    // 對應的還有childBeanDefinition,RootBeanDefinition / ChildBeanDefinition用來預定義具有parent/child關系的bean definition。
    // 所以RootBeanDefinition是一個可合並的BeanDefinition 
   if (!afterInvocationProviders.isEmpty()) {
      BeanDefinition afterInvocationManager;
      afterInvocationManager = new RootBeanDefinition(
            AfterInvocationProviderManager.class);
      afterInvocationManager.getPropertyValues().addPropertyValue("providers",
            afterInvocationProviders);
    // 建造者:然后將只做好的處理器beanDefinition添加到方法攔截器的beanDefinition中
      bldr.addPropertyValue("afterInvocationManager", afterInvocationManager);
   }

    // 建造者:在建造師處理好之后獲取最終的BeanDefinition
   BeanDefinition bean = bldr.getBeanDefinition();
   String id = pc.getReaderContext().generateBeanName(bean);
   pc.registerBeanComponent(new BeanComponentDefinition(bean, id));


   return new RuntimeBeanReference(id);
}


//關於切面的支持
// 如果打開了切面支持,則要為增強切面添加BeanDefinition
   if (useAspectJ) {
      BeanDefinitionBuilder aspect = BeanDefinitionBuilder
            .rootBeanDefinition("org.springframework.security.access.intercept.aspectj.aspect.AnnotationSecurityAspect");
      aspect.setFactoryMethod("aspectOf");
      aspect.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
      aspect.addPropertyValue("securityInterceptor", interceptor);
      String id = pc.getReaderContext().registerWithGeneratedName(
            aspect.getBeanDefinition());
      pc.registerBeanComponent(new BeanComponentDefinition(aspect
            .getBeanDefinition(), id));
   }
    // 如果需要,增加動態代理處理器
   else {
      registerAdvisor(pc, interceptor, metadataSource, source,
            element.getAttribute(ATT_ADVICE_ORDER));
      AopNamespaceUtils.registerAutoProxyCreatorIfNecessary(pc, element);
   }

  • 段落2:spring中關於解析自定義標簽生成BeanDefinition。(在模板模式筆記中,曾引用過這段,builder在spring中應用廣泛)
// 這個定義在AbstractSingleBeanDefinitionParser中,是我們自定義解析器的父類。
@Override
protected final AbstractBeanDefinition parseInternal(Element element, ParserContext parserContext) {
    // 建造者:獲取建造者,建造的對象是:GenericBeanDefinition

    //在執行我們自定義的解析器中的方法之前,先執行一些准備工作,也可以叫做預解析,對beanClass、scope、lazyInit等屬性的准備
   BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition();
   String parentName = getParentName(element);
   if (parentName != null) {
      builder.getRawBeanDefinition().setParentName(parentName);
   }
    // 這個是自定義解析器中重寫的getBeanClass方法
   Class<?> beanClass = getBeanClass(element);
   if (beanClass != null) {
    // 建造者:我們取得了beanClass,它將作為對象的零件,先‘安裝’在對象上
      builder.getRawBeanDefinition().setBeanClass(beanClass);
   }
   else {
        // 如果沒有重寫getBeanClass方法,就看有沒有重寫getBeanClassName方法;
      String beanClassName = getBeanClassName(element);
      if (beanClassName != null) {
      //  建造者:如果取得了beanClassName,‘安裝’在對象上
         builder.getRawBeanDefinition().setBeanClassName(beanClassName);
      }
   }
   // 建造者:安裝其他零件
   builder.getRawBeanDefinition().setSource(parserContext.extractSource(element));
   BeanDefinition containingBd = parserContext.getContainingBeanDefinition();
   if (containingBd != null) {
        //如果存在父類,就使用父類的scope屬性
      // Inner bean definition must receive same scope as containing bean.
      builder.setScope(containingBd.getScope());
   }
   if (parserContext.isDefaultLazyInit()) {
        //  建造者:安裝‘零件’懶加載方式。設為延遲懶加載
      // Default-lazy-init applies to custom bean definitions as well.
      builder.setLazyInit(true);
   }
    // 模板模式:doParse被我們自定義的解析器重寫;實現模板模式。
   doParse(element, parserContext, builder);
   //  建造者:獲取最終的BeanDefiniton對象(最開始的GenericBeanDefinition)。
   return builder.getBeanDefinition();
}

總結

先保持一個思想:建造者模式很簡單。
其次:建造者模式很靈活,如果能解讀框架源碼,會發現在創建對象這個功能上,建造者模式應用非常廣。
最后:以上可能不能完全描述建造者的精髓,本人很看重這個模式。查看結構圖,我們其實可以做很多的擴展:

  • builder也可以配合繼承體系,制作更細節的建造者
  • 產品體系(被建造者體系),也可以很靈活,要注意哪些是放在流水線上的零件,哪些是歸屬於自己獨有的零件。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM