世界上并没有完美的程序,但是我们并不因此而沮丧,因为写程序就是一个不断追求完美的过程。
在讲构建器之前,先给大家看一段代码,这并不是反例,而是在我遇到构建器之前常用的一种对象构建方式
@Getter
public class User {
private String id;
private String name;
private String address;
pubic User setId (String id) {
this.id = id;
return this;
}
public User setName (String name) {
this.name = name;
return this;
}
public User setAddress (String address) {
this.address = address;
return this;
}
@Override
public String toString() {
return "User{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
", address='" + address + '\'' +
'}';
}
}
使用时是这样:
public static void main(String[] args) {
User user = User.create().setId("id").setName("name").setAddress("address");
}
尤其是做为传值的对象时,使用起来特别方便。
说到这里,我希望大家注意一件事,set方法我是为了返回对象,所以单独写,但是toString方法,我也是单独生成的而没有使用@ToString注解,这是特意的。因为在开发中我发现一个问题,@ToString注解返回的是get的结果,而不是字段的实际值,如果在get上做了一些中间不想让人看到的操作,尤其是网络传输时,还使用@ToString注解的话,会存在安全问题,这是我在做一个加解密时发现的,如果有类似需求的要特别注意了。
这里举个例子让大家看一下
这是测试代码,大家看到了,每个get方法我都做了加1操作:
class MyUserDemo {
static class User {
private String id;
private String name;
private String address;
public User setId(String id) {
this.id = id;
return this;
}
public User setName(String name) {
this.name = name;
return this;
}
public User setAddress(String address) {
this.address = address;
return this;
}
public static User create () {
return new User();
}
public String getId() {
return id+1;
}
public String getName() {
return name+1;
}
public String getAddress() {
return address+1;
}
@Override
public String toString() {
return "User{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
", address='" + address + '\'' +
'}';
}
}
static class Use {
public static void main(String[] args) {
User user = User.create().setId("id").setName("name").setAddress("address");
System.out.println(user);
}
}
}
使用自己生成的toString方法输出结果:
User{id='id', name='name', address='address'}
然后是@ToString注解的版本:
class MyUserDemo {
@ToString
static class User {
private String id;
private String name;
private String address;
public User setId(String id) {
this.id = id;
return this;
}
public User setName(String name) {
this.name = name;
return this;
}
public User setAddress(String address) {
this.address = address;
return this;
}
public static User create () {
return new User();
}
public String getId() {
return id+1;
}
public String getName() {
return name+1;
}
public String getAddress() {
return address+1;
}
}
static class Use {
public static void main(String[] args) {
User user = User.create().setId("id").setName("name").setAddress("address");
System.out.println(user);
}
}
}
结果是这样的:
MyUserDemo.User(id=id1, name=name1, address=address1)
后面都加了一个‘1’,是get中加的。
在调用toString方法时,有些值尤其是中间值,是不希望被被人看到的,如加解密。这种情况可能比较少出现,但是一出现就是安全漏洞,希望大家注意。当然只是注意,平常使用@ToString注解是没有任何问题的。
接下来看一下构建器的实现方式:
class MyUserDemo {
@Data
@RequiredArgsConstructor
static class User {
private final String id;
private final String name;
private final String address;
public static class Builder {
private String id;
private String name;
private String address;
public Builder () {}
public Builder id(String id) {
this.id = id;
return this;
}
public Builder name (String name) {
this.name = name;
return this;
}
public Builder address (String address) {
this.address = address;
return this;
}
public User build () {
return new User(this);
}
}
public User (Builder builder) {
id = builder.id;
name = builder.name;
address = builder.address;
}
public enum Status {
OK, ERROR
}
public static Status getStatus (Object o) {
return (User.Status) o;
}
}
static class Use {
public static void main(String[] args) {
User user = new User.Builder().id("id").name("name").address("address").build();
System.out.println(user);
}
}
}
这是effective java中的原生实现,之所以比较喜欢,是因为对象一旦赋值,就不会在改变,在某种程度上保证了传值的安全。
还有就是,大家有没有注意到User中static的getStatus方法?
这个方法其实就是静态工厂方法的一种类型转换的使用场景,将静态工厂方法嵌入到有构建器的类中使用,除了类本身,也可以提供内部类的类型转换,不仅方便,也很优雅,(这是我今天在开发中发现并使用的一种情况,现在推荐给大家,希望大家喜欢)括号中的内容现在不再是我的选择,因为使用final作为属性的类,是不能自动赋值的,作为数据库的实体有特别大的限制,只能作为传值对象使用,因此在实际使用中我还是做了改进:代码规范-对象的创建-构建器的缺点以及改进。