zoukankan      html  css  js  c++  java
  • 建造者模式及应用举例

    模式名和分类

    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也可以配合继承体系,制作更细节的建造者
    • 产品体系(被建造者体系),也可以很灵活,要注意哪些是放在流水线上的零件,哪些是归属于自己独有的零件。
  • 相关阅读:
    Redis 设计与实现(第三章) -- 链表adlist
    Redis 设计与实现(第二章) -- SDS
    MySQL索引背后的数据结构及算法原理(转)
    MySQL索引原理及慢查询优化(转)
    MySQL常见的一些面试题(未完待续)
    js 获取前天、昨天、今天、明天、后天的时间
    linux 下nohup 使用
    java split 分割字符串用法
    Python 获取URL访问的HEAD头信息
    MySQL日期数据类型、时间类型使用总结
  • 原文地址:https://www.cnblogs.com/dhcao/p/11393459.html
Copyright © 2011-2022 走看看