面试官:builder模式如何使用的?给我说说

2020/2/25 17:15:53

本文主要是介绍面试官:builder模式如何使用的?给我说说,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

Builder 项目使用

前几天介绍了Builder模式,对于Builder模式看懂还是很简单的没有看过的小伙伴可以看一下建造者模式

今天来介绍一下Build设计模式在项目中的使用 ,项目是学校和同学一起写的校园小程序 抽出其中使用模式的部分来献丑了。

博客分为两个部分

  • 解释builder模式在web项目中的出现的原因和原理
  • 代码阐释如何使用builder模式,使用注意的地方
  • 自己项目中使用的builder模式,模式落地

可以用Builder模式处理的问题

在OOD的设计中,经常会出现一些类,他们拥有十几个成员变量,但是其中只有几个是必填的,其他的都是可选的,而我们要获取这种对象的方式,常规上有两种,一种是工厂方法,一种是构造器,从某种意义上来说,这两种方法实际上是一种方法,都需要我们手动的填入所有参数,或者提供若干个构造器,书上将这种模式称为 重叠构造器模式 (telescoping constructor)

public class Dto {
   //使用final表示这个类属性为必须初始化所以构造函数的形参中必须有这个值
   private final String number1;
   private final String number2;
   private String number3;
   private String number4;
   private String number5;
   private String number6;

   public Dto(String number1, String number2, String number3, String number4, String number5, String number6) {
       this.number1 = number1;
       this.number2 = number2;
       this.number3 = number3;
       this.number4 = number4;
       this.number5 = number5;
       this.number6 = number6;
   }

   public Dto(String number1, String number2, String number3, String number4, String number5) {
       this.number1 = number1;
       this.number2 = number2;
       this.number3 = number3;
       this.number4 = number4;
       this.number5 = number5;
   }

   public Dto(String number1, String number2, String number3, String number4) {
       this.number1 = number1;
       this.number2 = number2;
       this.number3 = number3;
       this.number4 = number4;
   }

   public Dto(String number1, String number2, String number3) {
       this.number1 = number1;
       this.number2 = number2;
       this.number3 = number3;
   }

   public Dto(String number1, String number2) {
       this.number1 = number1;
       this.number2 = number2;
   }
}
复制代码

可以看出上例,必填属性有两个,可选属性有四个,而我们需要提供的构造器,居然多达5个,这不仅使得代码的书写上变得繁琐,对于可读性来说也是一个很大的影响。试想一下,如果我们把number1number2的值弄反了,编译器也不会报错,因为它们都是String类型。

如何解决重叠构造器模式带来的弊端

从上面的的例子可以看出,对于这种复杂的对象,使用重叠构造器模式并不是一种很好的选择,有一种比重叠构造器更为有效的方法,叫做 JavaBean模式,这应该不是什么很高深的模式,从Java类的角度出发,这只是面向对象封装性的体现罢了。还是上面的例子,只不过我们换种写法

public class DtoMain {
    private String number1;
    private String number2;
    private String number3;
    private String number4;
    private String number5;
    private String number6;

    public String getNumber1() {
        return number1;
    }

    public void setNumber1(String number1) {
        this.number1 = number1;
    }

    public String getNumber2() {
        return number2;
    }

    public void setNumber2(String number2) {
        this.number2 = number2;
    }

    public String getNumber3() {
        return number3;
    }

    public void setNumber3(String number3) {
        this.number3 = number3;
    }

    public String getNumber4() {
        return number4;
    }

    public void setNumber4(String number4) {
        this.number4 = number4;
    }

    public String getNumber5() {
        return number5;
    }

    public void setNumber5(String number5) {
        this.number5 = number5;
    }

    public String getNumber6() {
        return number6;
    }

    public void setNumber6(String number6) {
        this.number6 = number6;
    }

    public static void main(String[] args) {
        DtoMain dtoMain=new DtoMain();
        dtoMain.setNumber1("as");
        dtoMain.setNumber2("123");
        
    }
}
复制代码

可以看出这种方式避免了创建大量的构造器,甚至可以直接使用默认的构造器,当然这种方式也不是没有弊端,最大的弊端就是,将原本一次完成的初始化操作,变成了多次操作,这很像数据库中的事务,原本是一个批次全部处理,现在变成了一条一条处理,整个初始化操作的完整性得不到保证。可能会使代码出现潜在的风险,而且这种风险很难被探查出来。而且注意到它取消了属性的final关键字修饰,这使得这种类型的类在多线程的使用中需要调用者做出额外的操作以保证线程安全。其次多个属性的set操作在实际项目编写时容易遗漏出现bug。

public class DtoBuilder {
    private final String number1;
    private final String number2;
    private final String number3;
    private final String number4;
    private final String number5;

    public static class builder {
        private final String number1;
        private final String number2;
        private String number3 = "";
        private String number4 = "";
        private String number5 = "";

        public builder(String number1, String number2) {
            this.number1 = number1;
            this.number2 = number2;
        }

        public builder builderNumber3(String number3) {
            this.number3 = number3;
            return this;
        }

        public builder builderNumber4(String number4) {
            this.number4 = number4;
            return this;
        }

        public builder builderNumber5(String number5) {
            this.number5 = number5;
            return this;
        }

        public DtoBuilder getInstance() {
            return new DtoBuilder(this);
        }
    }

    public DtoBuilder(builder thisBuilder) {
        this.number1 = thisBuilder.number1;
        this.number2 = thisBuilder.number2;
        this.number3 = thisBuilder.number3;
        this.number4 = thisBuilder.number4;
        this.number5 = thisBuilder.number5;
    }

    public static void main(String[] args) {
        DtoBuilder dtoBuilder = new DtoBuilder.builder("1", "2")
                        .builderNumber3("3").
                        builderNumber4("4").
                        builderNumber5("5").
                        getInstance();
    }
}
复制代码

可以看到上面的代码,首先发现Member的所有成员变量都是final修饰的,这意味着这个类是线程安全的,其次注意到Builder方法中,只有两个必填的字段是final修饰,其他可选的都没有用final修饰,其实这里之所以这么做是为了强制编译器进行类型检查,因为final对象要求必须先提供好值,或在构造器中完成初始化,这里用final修饰两个必填字段只是为了检查是否提供了包含两个必填字段参数的构造器。而Builder类中的各个可选变量方法(age(), cellPhoneNumber()...)的返回值都是Builder对象本身,这又构成了链式调用,怎么评价这链式调用呢,在写的时候挺爽的,一直.下去就行了,但是实际上反对者也有蛮多的,看人吧。实际上如果讲可选变量方法的返回值改成void就变成了Java Bean模式,实际上因为required成员变量的值已经被构造器完成了赋值操作后,在我看来这就是赤裸裸的getter/setter。另外需要大家注意的一点是final定义的参数是内容可以改变但值不能改变所以在修改的时候无法直接对已经初始化的final String 变量重新复制 即 不能在DtoBilder中对number1变量设置值,因为String是放置在常量池中,改变String是直接创建一个新String放在常量池中,地址肯定改变。有不了解的同学可以看看我的java虚拟机基础复习

总结

因为在正常的类中多增加了一个Builder类,所以在创建对象时会存在一些额外的开销。在一些对性能要求很严苛的情况下应当避免使用Builder模式,在可变参数的数量较少时(小于4个)应当避免使用Builder模式,而应考虑重叠构造器模式,这会使得代码变得更为紧凑。除非这个类的成员变量个数可预见的会在未来增长,这个时候应当提前使用Builder模式已减小未来重构代码的代价。

项目中实际开发使用的builder模式

先来看一段代码,这是一个活动类的Builder类,声明为final表示这个类不可以被继承。有小伙伴可能有疑问了为什么上面的builder模式是内部静态类,这边却单独创建了一个新类。原因是这样的,在实际项目编写中经常会忘记这个类是否有生成内部静态类,导致使用时最快的方式是new 生成类.内部类 的反射方式来查看,同时如果要为一个类新增builder模式必须到这个类内部去修改,这是不符合ocp原则的。所以方便起见,单独创建了一个Builer包,在其中放置了这些创建者类。

public final class ActivityBOBuilder {
    /**
     * 活动id
     */
    private String activityId;

    /**
     * 活动名
     */

    private String activityName;

    /**
     * 活动类型
     */

    private String type;

    /**
     * 单位信息
     */

    private String organizationMessage;

    /**
     * 活动地点
     */
    private String location;

    /**
     * 活动开始时间
     */
    private Date start;

    /**
     * 活动结束时间
     */
    private Date end;

    /**
     * 活动分数
     */
    private Long score;

    /**
     * 活动描述
     */
    private String description;

    /**
     * 活动创建者
     */
    private String creatorId;

    /**
     * 活动状态
     */
    private String state;

    /**
     * 活动学期
     */
    private String term;

    /**
     * 拓展信息
     */
    private Map<String, String> extInfo = new HashMap<>();


    private ActivityBOBuilder() {
    }

    public static ActivityBOBuilder getInstance() {
        return new ActivityBOBuilder();
    }

    public ActivityBOBuilder withActivityId(String activityId) {
        this.activityId = activityId;
        return this;
    }

    public ActivityBOBuilder withActivityName(String activityName) {
        this.activityName = activityName;
        return this;
    }

    public ActivityBOBuilder withType(String type) {
        this.type = type;
        return this;
    }

    public ActivityBOBuilder withOrganizationMessage(String organizationMessage) {
        this.organizationMessage = organizationMessage;
        return this;
    }

    public ActivityBOBuilder withLocation(String location) {
        this.location = location;
        return this;
    }

    public ActivityBOBuilder withStart(Date start) {
        this.start = start;
        return this;
    }

    public ActivityBOBuilder withEnd(Date end) {
        this.end = end;
        return this;
    }

    public ActivityBOBuilder withScore(Long score) {
        this.score = score;
        return this;
    }

    public ActivityBOBuilder withDescription(String description) {
        this.description = description;
        return this;
    }

    public ActivityBOBuilder withCreatorId(String creatorId) {
        this.creatorId = creatorId;
        return this;
    }

    public ActivityBOBuilder withState(String state) {
        this.state = state;
        return this;
    }

    public ActivityBOBuilder withTerm(String term) {
        this.term = term;
        return this;
    }

    public ActivityBOBuilder withExtInfo(Map<String, String> extInfo) {
        this.extInfo = extInfo;
        return this;
    }

    public ActivityBO build() {
        ActivityBO activityBO = new ActivityBO();
        activityBO.setActivityId(activityId);
        activityBO.setActivityName(activityName);
        activityBO.setType(type);
        activityBO.setOrganizationMessage(organizationMessage);
        activityBO.setLocation(location);
        activityBO.setStart(start);
        activityBO.setEnd(end);
        activityBO.setScore(score);
        activityBO.setDescription(description);
        activityBO.setCreatorId(creatorId);
        activityBO.setState(state);
        activityBO.setTerm(term);
        activityBO.setExtInfo(extInfo);
        return activityBO;
    }
}
复制代码

如果以上内容有问题,欢迎跟我联系,有不懂的小伙伴也可以私信,大家一起讨论



这篇关于面试官:builder模式如何使用的?给我说说的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程