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也可以配合继承体系,制作更细节的建造者
    • 产品体系(被建造者体系),也可以很灵活,要注意哪些是放在流水线上的零件,哪些是归属于自己独有的零件。
  • 相关阅读:
    centos 安装 TortoiseSVN svn 客户端
    linux 定时任务 日志记录
    centos6.5 安装PHP7.0支持nginx
    linux root 用户 定时任务添加
    composer 一些使用说明
    laravel cookie写入
    laravel composer 安装指定版本以及基本的配置
    mysql 删除重复记录语句
    linux php redis 扩展安装
    linux php 安装 memcache 扩展
  • 原文地址:https://www.cnblogs.com/dhcao/p/11393459.html
Copyright © 2011-2022 走看看