代码规范-对象的创建-使用构建器创建对象(传值时可以,但是有局限)

71 阅读4分钟

世界上并没有完美的程序,但是我们并不因此而沮丧,因为写程序就是一个不断追求完美的过程。

在讲构建器之前,先给大家看一段代码,这并不是反例,而是在我遇到构建器之前常用的一种对象构建方式

@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作为属性的类,是不能自动赋值的,作为数据库的实体有特别大的限制,只能作为传值对象使用,因此在实际使用中我还是做了改进:代码规范-对象的创建-构建器的缺点以及改进