Effective Java(一)———— 代替构造器和Setter的构建器模式

引言

Java语言中的一部经典著作《Effective Java》,里面涵盖了78条我们应该熟练的Java编程技巧。

本篇博客是该书学习的系列笔记第一篇。本系列博客不会与书中的78条建议完全匹配。只是以一种读者的身份来记录和总结从书中得到的好的编程建议,博客中会明确从书中哪条建议得来的知识。

本博客总结自书中第二条:遇到多个构造器参数时要考虑用构建器

构建器的产生

我们知道对象的构建有很多种方式:构造器、静态工厂、Setter。

通常,最傻瓜式的对象构建方式是:通过new关键字,调用一个默认的无参构造,然后向这个对象中set各种属性。

第二种稍微上点档次的方式是使用构造器。为一个JavaBean创建一个有参构造,在new的时候通过有参构造来构建这个bean。

第三种是静态工厂,简单的说也是通过一个静态方法,来调用一个有参构造,来构建对象。

这三种方法貌似都没什么问题。但是设想一下这样的场景:

一个JavaBean中有很多属性,大于5个,这些属性有些是必需的,有些是可选的,如何来选择构建对象的方式?

上述三种方式:有参构造、静态工厂、Setter属性,依然可以达到场景的要求。但是它们在面对大量可选参数的情况下并不能很好的扩展。原因如下:

有参构造(或静态工厂):需要提供针对可选参数的多个不同参数列表的构造器,从而满足不同场景下对可选参数的赋值,不同构造器之间参数列表的差别很细微,可读性非常差,这种方式叫“重叠构造器”。

Setter属性:这种方式虽然比较灵活,但是有可能有线程安全问题,因为在调用构造器与set方法之间,对象并未完全构建完毕,这个时候如果在多线程的环境下有可能使未完成构建的对象处于一种不安全的状态。这种方式叫“JavaBeans模式”。

总之一句话:重叠构造器(或重叠静态工厂)可以构造出线程安全的JavaBean,但是可读性差,扩展性不强;而JavaBeans模式,虽然可读性好,灵活,但是不具备线程安全性,可能发生线程安全问题。

这就产生了新的构造对象的方式:构建器。它是“建造者模式”的一种形式,同时具备灵活性、线程安全性、可读性等多种优点,但是可能也要面临额外的编码量、理解难度、可能存在的性能开销等问题。一般在某个对象含有大量可选参数的情况下使用构建器

构建器的使用

说了这么多,到底构建器是什么?我们如何使用构建器呢?

首先需要明确一点,构建器是用于构建一个对象的。它兼顾了构造器的优点和setter的优点。

客户端使用一个构建器需要完成三件事:

1、不直接生成目标对象,而是让客户端代码利用所有必需参数调用构造器(或静态工厂)创建builder对象(builder对象用于后续目标对象的创建和赋值工作)。

2、客户端在builder对象上调用类似Setter方法,来设置每个可选参数。

3、客户端调用无参的builder()方法生成目标对象。

 因此,客户端代码可以像下面这样:

// 客户端代码使用必需参数创建一个builder对象
Student stu = new Student.StudentBuilder(10001, "张三", 1, 17)// 必须参数:学号、姓名、性别、年龄.headTeacherName("张老师")  // 可选参数:班主任名称.className("三年一班")      // 可选参数:班级名称.address("朝阳区-潘家园")   // 可选参数:家庭住址.specialty("硬笔书法")      // 可选参数:特长.build();

 可以看到上面的代码中,一条语句就完成了对学生对象的创建,没有使用setter方法将可选参数的设置与构造器分开,因此不会存在线程安全问题,可读性也非常强,可选参数可以根据不同的场景自由剪裁,因此扩展性也非常高。

构建器的创建

和构造器和Setter方法一样,我们同样需要在定义JavaBean对象的类中定义一个构建器,让它来完成对目标对象的构建工作。

同样,构建器的编码工作我们需要注意几个关键步骤:

1、构建器实际上是一个静态成员类

2、构建器类中拥有所有目标对象的属性

3、构建器类拥有设置必需参数的构造器设置每个可选参数的“类似Setter方法”

4、“类Setter方法”返回构建器对象本身

5、构建器有一个无参的build()方法,用于创建目标对象

6、目标类有唯一的私有构造器,构造器参数就是构建器对象,这个构造器的工作就是将参数中的每个属性一 一赋值给目标对象。

和前面客户端创建Student对象对应的Student类代码如下:

public class Student {// 必需参数private int stuNo;private String name;private int gender;// 性别:0代表女生;1代表男生private int age; // // 可选参数private String headTeacherName;// 班主任名称private String address;// 家庭住址private String className;//班级名称private String specialty;//特长private Student(StudentBuilder builder) {this.stuNo = builder.stuNo;this.name = builder.name;this.gender = builder.gender;this.age = builder.age;this.headTeacherName = builder.headTeacherName;this.address = builder.address;this.className = builder.className;this.specialty = builder.specialty;}/** 用于构建Student对象的构建器类*/public static class StudentBuilder {// 必需private int stuNo;private String name;private int gender;private int age;// 可选private String headTeacherName;// 班主任名称private String address;// 家庭住址private String className;//班级名称private String specialty;//特长public StudentBuilder(int stuNo, String name, int gender, int age) {this.stuNo = stuNo;this.name = name;this.gender = gender;this.age = age;}public StudentBuilder headTeacherName(String headTeacherName) {this.headTeacherName = headTeacherName;return this;}public StudentBuilder address(String address) {this.address = address;return this;}public StudentBuilder className(String className) {this.className = className;return this;}public StudentBuilder specialty(String specialty) {this.specialty = specialty;return this;}public Student build() {return new Student(this);}}
}

建议给Student添加一个toString()方法,并执行一下客户端代码:通过构建器创建Student对象并输出它。

可以看到,使用构建器的方式确实增加了一部分编码量,因此,并不是以后所有的情况都是用构建器模式才算好的代码。这种构建器模式通常用于参数较多的情况下。

总结

构建器可以解决因为大量可选参数使用重叠构造器导致客户端代码编码困难、代码可读性差、扩展性不强等缺点;同时具备类似Setter的灵活度和可读性。

同时,剖析了构建器实现的本质后,我们是否也可以简化这种构建器实现方式呢?当然可以,一个简单的JavaBean,让它的有参构造传入所有必需的参数,而它的Setter方法将void改为返回当前JavaBean对象不就完全可以达到这种串行构建对象的效果吗?书中构建器后半部分的阐述中提到了强加验证的思想,但并没有提供代码的实现思路和案例,因此在真正使用构建器的时候我们依然可以继续深入的研究构建器的使用方式,真正的掌握构建器的使用。

以上就是构建器的学习和使用,欢迎大家文末留言讨论。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/558894.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

LeetCode算法入门- Palindrome Number-day2

LeetCode算法入门- Palindrome Number-day2 Palindrome Number Determine whether an integer is a palindrome. An integer is a palindrome when it reads the same backward as forward. Example 1: Input: 121 Output: true Example 2: Input: -121 Output: false Ex…

JavaCard概述

什么是JavaCard JavaCard,即Java智能卡。以智能卡硬件系统为基础,通过软件的方式构造一个支持Java程序下载、安装、运行的软/硬件系统。由于引入了虚拟机技术,JavaCard具备硬件无关性,即智能卡应用程序开发与智能卡硬件系统相分离…

LeetCode算法入门- Add Two Numbers-day3

LeetCode算法入门- Add Two Numbers-day3 Add Two Numbers You are given two non-empty linked lists representing two non-negative integers. The digits are stored in reverse order and each of their nodes contain a single digit. Add the two numbers and return …

Java并发编程实战————并发技巧小结

可变状态是至关重要的。所有的并发问题都可以归结为如何协调对并发状态的访问。可变状态越少,就越容易确保线程安全性。尽量将域声明为final类型,除非需要它们是可变的。不可变对象一定是线程安全的。不可变对象能极大地降低并发编程的复杂性。它们更为简…

Java核心篇之多线程---day1

Java面试之多线程—day1 一. 线程中sleep方法与wait方法有什么区别? 对于 sleep()方法,我们首先要知道该方法是属于 Thread 类中的。而 wait()方法,则是属于Object 类中的。 在调用 sleep()方法的过程中, 线程不会释放对象锁。而…

LeetCode算法入门- Longest Substring Without Repeating Characters-day4

LeetCode算法入门- Longest Substring Without Repeating Characters-day4 Longest Substring Without Repeating Characters Given a string, find the length of the longest substring without repeating characters. Example 1: Input: “abcabcbb” Output: 3 Explana…

Java 多线程 —— 常用并发容器

引言 本博客基于常用的并发容器,简单概括其基本特性和简单使用,并不涉及较深层次的原理分析和全面的场景用法。 适合对不了解并发容器的同学,工作中遇到类似的场景,能够对文中提到的并发容器留有简单印象就好。 一、Concurrent…

LeetCode算法入门- Longest Palindromic Substring-day5

LeetCode算法入门- Longest Palindromic Substring-day5 Longest Palindromic Substring Given a string s, find the longest palindromic substring in s. You may assume that the maximum length of s is 1000. Example 1: Input: “babad” Output: “bab” Note: “a…

Tomcat运行三种模式:http-bio|http-nio|http-apr介绍

转自《tomcat运行三种模式:http-bio|http-nio|http-apr介绍》 Tomcat是一个小型的轻量级应用服务器,也是JavaEE开发人员最常用的服务器之一。不过,许多开发人员不知道的是,Tomcat Connector(Tomcat连接器)有bio、nio、apr三种运行模式&#…

LeetCode算法入门- Reverse Integer-day6

LeetCode算法入门- Reverse Integer-day6 Given a 32-bit signed integer, reverse digits of an integer. Example 1: Input: 123 Output: 321 Example 2: Input: -123 Output: -321 Example 3: Input: 120 Output: 21 class Solution {public int reverse(int x) {long…

Java工具方法——属性拷贝方法:BeanUtils.copyProperties(Object, Object)

介绍 org.springframework.beans.BeanUtils.copyProperties(Object, Object)是spring 框架的对象工具类:BeanUtils下的一个拷贝对象属性的方法。 官方注释 把给定的源对象属性值拷贝到目标对象中。 注意:源对象类与目标对象类不一定非要完全匹配&…

LeetCode算法入门- String to Integer (atoi)-day7

LeetCode算法入门- String to Integer (atoi)-day7 String to Integer (atoi): Implement atoi which converts a string to an integer. The function first discards as many whitespace characters as necessary until the first non-whitespace character is found. The…

LeetCode算法入门- Roman to Integer Integer to Roman -day8

LeetCode算法入门- Roman to Integer -day8 Roman to Integer: 题目描述: Roman numerals are represented by seven different symbols: I, V, X, L, C, D and M. Symbol    Value I        1 V        5 X        10 L…

Git初学札记(九)————EGit检出远程分支

引言 现在有这样一个使用场景:团队中的其他开发者提交了一个新的特性分支(如feature_1),要求我们一同开发,并将自己修改的代码也全部提交到这个分支上去。那么如何将这个分支检出,并将本地检出的分支与这个…

LeetCode算法入门- 3Sum -day9

LeetCode算法入门- 3Sum -day9 题目描述: Given an array nums of n integers, are there elements a, b, c in nums such that a b c 0? Find all unique triplets in the array which gives the sum of zero. Note: The solution set must not contain dup…

XML学习(一)————XML简介

引言 作为数据传输界鼎鼎大名的扛把子,XML被应用于各个方面,但随着弱结构化标记语言如JSON、YAML等的出现,人们慢慢的脱离了XML的统治,但在互联网早期的发展当中XML是不可或缺的一部分,比如各种微信开发中的数据传输&…

Java核心篇之Java锁--day2

Java核心篇之Java锁–day2 乐观锁:乐观锁是一种乐观思想,即认为读多写少,每次去取数据的时候都认为其他人不会修改,所以不会上锁;但是在更新的时候会判断一下在此期间别人有没有去修改它,如果有人修改的话…

XML学习(二)————属性还是标签?

引言 xml中并没有规则要求我们什么时候使用属性,什么时候使用标签。 属性和标签都可以存储数据,但是在XML的使用中,我们需要探讨一下对属性和标签的选择问题。 约定规则 XML 应该避免使用属性来存储数据,这与HTML的推荐规则不…

LeetCode算法入门- 3Sum Closest -day10

LeetCode算法入门- 3Sum Closest -day10 Given an array nums of n integers and an integer target, find three integers in nums such that the sum is closest to target. Return the sum of the three integers. You may assume that each input would have exactly one …

Spring Boot————Web应用启动时自动执行ApplicationListener用法

原文:《web服务启动spring自动执行ApplicationListener的用法》 引言 我们知道,一般来说一个项目启动时需要加载或者执行一些特殊的任务来初始化系统,通常的做法就是用servlet去初始化,但是servlet在使用Spring bean时不能直接注…