作者:大宽宽
链接:https://www.zhihu.com/question/326142180/answer/697172067
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

使用Builder大概有两个用途

  1. 解决具有大量参数的构造函数不好用的问题
  2. 解决让Object始终保持valid状态的问题

下面分别说说两个用途。

Java支持使用constructor初始化一个object,但是如果object的成员非常多,constructor就不好用了。巨长的参数列表很难分清楚哪个是哪个。这时最好用某种方式让哪个字段是哪个更加清晰一点。Builder模式算是可以解决这个问题。此外题主在题目中那种让setter返回this的形式也可以解决。并且,能用"."连接下来配合IDE的自动提示还能够提高编码速度,这算是一个额外的便利。

插播一下这种返回this的setter并不符合java bean的setter规范。但一般来讲,严格遵守这个规范的意义不大。

构造函数不够好用是Java语法的一个根本性问题。有其他语言对此有更好的语法支持,比如kotlin,本来就支持用key=value的形式初始化一个object:

class Foo (name:String, age:Int)
// ...
val bar = Foo(age = 3, name = "John")

再比如javascript完全可以用object literal这样做。

let foo = new Foo({key1: "VALUE1“,key2: 2,key3: 3.15,key4: true
});

尽管如此,不使用Builder,直接使用setter可能会得到一个状态不完整的object。这就是第二点作用。也是题主写法解决不了的。

如果追一下oop设计的本源,一个object从出生开始到被销毁,应该始终维护一种“不变量“(invariant),或者叫做始终处于valid状态。

比如,设计了一个Rectangle类,有两个字段width和height。为了让object的状态始终valid,我要求width和height必须都是正数,并且width与height之积不得超过100。假设class的定义是这样的:

public class Rectangle {private int width;private int height;
}

如果要用setter初始化的话,那么object就会在完全被set好之前处于“invalid”的状态。

Rectangle r = new Rectange(); // r is invalid, not good
r.setWidth(2); // r is invalid, not good
r.setHeight(3); // r is valid

为了避免这种invalid状态的发生,就要求使用构造函数一次性初始化好所有的成员。但是如果是一个很多成员的class,构造函数不好用,那么唯一合理的办法就是做一个builder。这样builder就可以分多步初始化所有成员,build的结果出来就是一个处于valid状态的object。

当然,这个事情并不一定要这么解决,比如如果业务允许,你可以给Rectangle的成员设置合理的初始值,然后再用setter改,像这样:

public class Rectangle {private int width = 1;private int height = 1;
}
Rectangle r = new Rectangle();
r.setWidth(3);
r.setHeight(4);

有一类很特别的object就是“不可变object”。不可变是个很好的避免程序出问题的方法。具体“为什么不可变是一件好事情”的原因这里不展开了。为了得到一个不可变的object,是不可能使用任何setter方法的,必须使用构造函数一上来就把所有数据都设置好。因为多参数构造函数不好用,所以这里就得靠builder。

public class Rectangle {private final int width;private final int height;public Rectangle(int w, int h) {if (w <= 0 || h <= 0 || w * h > 100) {throw new RuntimeException("this rectangle is not valid!");}this.width = w; // 留意因为是final,所以必须在这里就初始化this.height = h; }
}public class RectangleBuilder {private int width;private int height;public RectangleBuilder setWidth(int w) {this.width = w; return this;)public RectangleBuilder setHeight(int h) {this.height = h; return this;}public Rectangle build() { return new Rectangle(this.width, this.height); }
}RectangleBuilder rb = new RectangleBuilder();
rb.setWidth(3).setHeight(2);
Rectangle r = rb.build();

java对于final成员的要求是最晚构造函数得初始化,否则编译报错。这在有些时候不太好用。我们可能希望某个字段在第一次设置后就可以保持不变了。kotlin有个lazyinit的保留字实现了这个特性。
此外,上面这一坨代码就是Builder模式的正规写法,非常的繁琐。好在有Lombok的@Builder帮忙自动生成,不需要手写。

此外,也许你并不在意对象一直处于valid状态,只要在真正使用成员干活之前确保valid就行。那么就直接在干活前加判断是否valid就好。

public class Rectangle {private int width;private int height;// ...public draw() {if (width <= 0 || height <= 0 || width * height > 100) {throw new RuntimeException("this rectangle is not valid!");}// do the real drawing work }
}

如果这样做都不够灵活,你甚至都可以做一个public isValid()的方法让外界在调用关键动作之前,手动先验证Object的状态是valid。

还有一大类Object其实是“Data Object”,即用来做数据结构的。比如函数间传递一些参数,从接口或者数据库读出来的数据要有个存的地方等。这类Object压根就没什么“valid状态”一说,或者说,其是否合法完全是看业务场景的上下文,难以仅通过Object里数据本身就能判定的。对于这类object,直接用java bean规范new一个出来,然后挨个set就好。或者,按照我的想法,setter都是多余的,全部public成员直接赋值就足够了。

注意区分Data Object和面向对象里的那个Object,它们本质上是不同的东西

总结一下,如果你用Java,并且:

  • 你的类里的成员很多
  • 你希望维持object自始至终处于valid状态/不可变

那么你需要一个builder。至于是不是内部类我觉得都可以。也许内部类会让人觉得“XXX.Builder属于XXX“,感觉上好些。

但是做了Builder后,还要做些额外工作告诉类的使用者“你应该用builder来创建object,而不是直接new“,这需要一些沟通、文档之类的工作量。

反之,如果:

  • 你的object字段数量很少,构造函数够用了
  • 你压根就不在意object始终处于valid状态,或者你有别的规范来约束object是不是能用来干活
  • 你的object是“data object”

那八成就不太需要做个builder。题主的写法也许已经足够好了。

对于一些语言,如kotlin,javascript,scala,python等,因为他们的语法本身就能支持builder的功能,基本上也就不需要手工实现builder了。

有帮到你的点赞、收藏一下吧

需要更多教程,微信扫码即可

Builder内部类相关推荐

  1. Android设计模式之——Builder模式

    一.介绍 Builder模式是一步一步创建一个复杂对象的创建型模式,它允许用户在不知道内部构建细节的情况下,可以更精细的控制对象的构造流程.该模式是为了将构建复杂对象的过程和它的部件解耦,使得构建过程 ...

  2. 实体类里的内部类怎么单独赋值_java你还在用各种setter赋值初始对象吗?用设计模式化简为易...

    我在公司也看到了很多的系统了.当我看到一个结构清晰.代码清晰的代码时会称赞那些能写这样代码的人真是太棒了.看到乱七八糟的代码注释时也会抱怨吐槽"这是什么代码啊"? 看看别人的代码, ...

  3. 2.lombok系列2:lombok注解详解

    转自:https://www.imooc.com/article/18157 开篇 看到第一篇<初识lombok>你可能意犹未尽,本文我们按照场景来介绍一下常用的注解. 未特别说明,均标注 ...

  4. 快来看看Google出品的Protocol Buffer,别仅仅会用Json和XML了

    前言 习惯用 Json.XML 数据存储格式的你们,相信大多都没听过Protocol Buffer Protocol Buffer 事实上 是 Google出品的一种轻量 & 高效的结构化数据 ...

  5. Android 常用开源框架源码解析 系列 (四)Glide

    一.定义  Glide 一个被google所推荐的图片加载库,作者是bumptech.对Android SDk 最低要求是 API 10  与之功能类似的是Square公司的picasso  二.基本 ...

  6. Protocol Buffers的应用与分析

    Protocol Buffers的应用与分析 明尘 1  Protocol Buffers的介绍 Protocol Buffers是一种用于序列化结构化数据的机制,它具有灵活.高效.自动化的特点.类似 ...

  7. Lombok注解使用详解

    本文来说下Lombok注解使用 文章目录 lombok的常用注解 @Builder注解 @Accessors注解 本文小结 lombok的常用注解 lombok的常用注解 @Getter和@Sette ...

  8. 第 3 章 MybatisPlus 注入 SQL 原理分析

    第 3 章 MybatisPlus 注入 SQL 原理分析 思考问题 我们编写的 UserMapper 继承了 BaseMapper<T>,就拥有了基本的增删改查功能,这是因为 BaseM ...

  9. java实现物体下落效果_手撸一个物体下落的控件,实现雪花飘落效果

    效果图: 圣诞登录页.gif 参考文章: Android自定义View--从零开始实现雪花飘落效果 感谢原文作者,不仅实现了效果,并且写得非常详细,还做了优化.笔者参考原文作者的源码,做了一点修改,实 ...

最新文章

  1. 用户身份验证配置需考虑的因素
  2. centos7.3修改mysql密码_Centos7.3下mysql5.7.18安装并修改初始密码的方法
  3. 《漫画算法2》源码整理-2 图算法
  4. SOA和微服务之间的区别(应用和数据的垂直拆分水平拆分)
  5. React-Native学习笔记
  6. 转载:售前十年,你在第几年
  7. JFinal针对ORACLE的timestamp字段解决办法
  8. iOS之UI--CAShapeLayer
  9. kettle的下载|运行及简单kettle数据抽取(MAC kettle|windows Kettle)
  10. 基于协同过滤的推荐算法
  11. 信息系统软件配置、过程管理、开发工具(详细介绍)
  12. 正则表达式(规则+代码)
  13. 64G的EXFAT格式的U盘如何格式化为FAT32
  14. 世界各地 史上最全最详细无线通信频率分配表(内容含概wifi、2.4G、5G,绝对值得收藏)
  15. 空气净化器什么牌子好,家用空气净化器哪个牌子好推荐
  16. 荣耀v40pro+参数配置 荣耀v40pro+价格
  17. 求助:hmailserver+roundcube启用密码插件后,用户无法修改密码问题
  18. 程序人生-hello`s P2P
  19. USB设备运行不正常,windows无法识别的问题
  20. python中str,int,list,list(str),list(int)的相互转换

热门文章

  1. 3千内!苹果最便宜iPhone稳了:坐等
  2. 3万亿巨大市场,难怪马云、刘强东要抢着去养猪
  3. 那个把iPhone卖这么贵的女高管离开了 个人信息已从苹果官网撤下
  4. 中国移动:手机上网流量单价累计同比下降62%
  5. 从折叠屏到AR 三星Galaxy新品预热宣传片大招频现
  6. 女员工采购电影票抽中黄金 老板:必须上交 不上交就开除
  7. 拳王虚拟项目公社:闲鱼操作卖资源如何赚钱?闲鱼怎么卖虚拟资源?卖什么资源赚钱?
  8. 晨哥真有料丨女生眼里的满分男生!
  9. Android 音频开发(四) 如何播放一帧音频数据下
  10. WPF如何给窗口设置透明png的图片背景