这是我参与8月更文挑战的第3天,活动详情查看:8月更文挑战
欢迎来到今天的学习,今天我们一起来唠唠建造者模式。多唠叨几句,前面我有提到本月将会对java的设计模式精讲,欢迎点击头像,关注我的专栏,我会持续更新,加油!
系列文章:
....系列持续更新
话不多话,进入正题
建造者模式
建造者模式是另外一个高频使用的创建型设计模式——通常叫 Builder 模式,中文一般叫建造者模式或生成器模式。
今天我们改变下策略,先开始不讲原理,先从例子下手,从解决问题案例当中去寻求答案。你认为的那种答案。
先看建造者模式主要带来什么或者解决什么问题:
-
直接使用构造函数或者使用 set 方法来创建对象方不方便?,有没有一种简单的方法创建对象
-
存在的价值,为什么需要建造者模式创建对象
下面通过代码具体深入理解,我们只讲干货,让你看完就能解决现在你代码中的实际问题!
场景及案例分享
对同一种场景我们用建造者模式和不用建造者模式来进行代码对比:
很简单,我们创建学生对象
/**
* 学生实体
* 不用建造者模式
*/
public class Students {
private String name; //姓名
private int age; //年龄
private String grade; //班级
private String gender; //性别
public Student(String name, int age, String grade, String gender) {
this.name = name;
this.age = age;
this.grade = grade;
this.gender = gender;
}
public Student(String name, int age, String grade) {
this.name = name;
this.age = age;
this.grade = grade;
}
public Student(String name, int age) {
this.name = name;
this.age = age;
}
}
//上述代码写了三个不同参数的构造函数
public static void main(String[] args) {
Student student01 = new Student("张三",26,"13班","男");
Student student02 = new Student("张三",26,"13班");
...
}
上述有问题吗,没有问题。这代码可以吗,你觉得呢?问题显而易见(肯定还有很多小伙伴平常工作中都是这么搞的)。上面这段代码没有使用建造者模式,所以我们需要使用传统的 getter、setter 方法,并指定不同的入参来构造对象。如果参数过多,业务比较多,那需要创建太多构造方法。
而使用建造者模式后的类,功能却发生了完全不一样的变化,如下所示:
/**
* 学生实体
*
* 使用建造者模式创建对象
*/
public class Students {
//所有属性
private String name;
private int age;
private String grade;
private String gender;
public Students() {
}
//内部builder new对象
public static Students builder() {
return new Students();
}
//将属性作为步骤
public Students name(String name) {
this.name = name;
return this;
}
//将属性作为步骤
public Students age(int age) {
this.age = age;
return this;
}
//将属性作为步骤
public Students grade(String grade) {
this.grade = grade;
return this;
}
//将属性作为步骤
public Students gender(String gender) {
this.gender = gender;
return this;
}
//执行创建操作(最后封口return)
public Students build() {
validateObject(this);
return this;
}
private void validateObject(Students Students) {
//可以做基础预校验,或自定义校验
}
@Override
public String toString() {
return "Students{" +
"name='" + name + '\'' +
", age=" + age +
", grade='" + grade + '\'' +
", gender='" + gender + '\'' +
'}';
}
}
//用建造者模式来创建对象
public static void main(String[] args) {
//创建对象
Students st = Students.builder()
.name("张三")
.age("26")
.grade("13班")
.gender("男")
.build();
//创建对象
Students st01 = Students.builder()
.name("张三")
.age("26")
.grade("13班")
.build();
//一步步建造一个个属性
}
这,就是建造者模式来搞定对象。我们可以发现构建出了完全不同的对象实例,而使用传统的 getter、setter 方法,则需要写很多不同的构造函数来应对变化。
所以说,使用建造者模式能更方便地帮助我们按需进行对象的实例化,避免写很多不同参数的构造函数,同时还能解决同一类型参数只能写一个构造函数的弊端。
虽然上面案例的实现比较简单,但是也充分演示了如何使用建造者模式。在实际的使用中,通常可以直接使用 lombok 的 @Builder 注解实现类自身的建造者模式
为什么使用建造者模式来创建对象?存在的意义是什么
为什么要使用建造者模式来创建类?在我看来,有以下几点原因。
-
第一,分步骤的属性赋值更适合多次运算结果类创建场景。在面向对象软件开发中,很多时候创建类所需要的参数并不是一次都能准备好的,比如,计算订单优惠价格、查询库存状态等,有的参数可能需要通过调用多个服务运算后才能得出,这时我们可以根据已知参数预先对类进行创建,等有合适的参数时再设置类的属性,而不是等到所有结果都齐备后才去创建类。
-
第二,不需要关心特定类型的建造者的具体算法实现。 比如,我们在使用 StringBuilder 时,并不太关心它的具体代码实现,而是关心它提供给我们的使用功能。这在某些需要快速复用的场景中,能起到提升编码效率的作用。
-
第三,提高代码的可阅读性,简洁明了,需要哪个功能(属性),就调用哪个,方便阅读,可维护性强。内部属性扩展性极强,自由地组合对象属性。
-
第四满足开闭原则,这也是设计模式的优点。 每一个建造者都相对独立,因此能方便地进行替换或新增,这就大大提升了代码的可扩展性。
当然,夸奖完,还是要指出一些存在一些客观性存在的问题。
-
使用范围有限。 建造者模式所创建的对象一般都需要有很多的共同点,如果对象实例之间的差异性很大,则不适合使用建造者模式。
-
容易造成超类,业务繁多,新的创建步骤就会被加进来,这会造成代码量的急剧膨胀,类会变得臃肿,最终形成一个庞大的超大类。
Netty当中的建造者模式
我们来看下Netty框架当中是怎么运用建造者模式的(Netty不懂的自行百度下,后续我会对Netty作出专题来讲)。
大家还记得,在服务端启动的时候有个启动辅助类ServerBootStrap,我们调用group方法、channel方法设置参数。这里面也使用了这种建造者链式编程来设置相关参数。
ServerBootstrap b = new ServerBootstrap();
//开始创建对象
b.group(bossGroup, workerGroup) //bossGroup和workerGroup 先不用管,本节主要讲建造者模式
//赋值channel参数
.channel(NioServerSocketChannel.class)
//赋值option参数
.option(ChannelOption.SO_BACKLOG, 100)
//赋值handler参数
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
System.out.println("服务启动");
}
});
下面我们来一起看下源码,源码我就直接从idea当中截图了,我会从中圈出重点:
先看group,设置完参数之后,便返回this对象
再看 childHandler,依然如此,是不是和我们上面学生的例子是一样的
其他几个参数也是一样,大家可以自行看下。
总结
OK,今天我们的建造者模式就讲到这里,感谢你的阅读,相信你能从中学到东西。同时也非常欢迎您的点赞,关注和分享!
我已经将本章收录在专题里,点击文章下方专题,关注专栏,我会每天发表干货,本月我会持续输入设计模式。我们下期再见!