用Builder模式代替重叠构造器模式和JavaBeans模式

场景:当一个类的构造行数或者静态工厂方法(出门右转)拥有多个传参并且会根据传参的数量生成不同对象的时候,往往容易写出难以阅读和维护的代码,比如:

public class Demo {
	private int p1;
	private String p2;
	private boolean p3;
	...
	public Demo(int p1) {
		this(p1, null);
	}
	public Demo(int p1, String p2) {
		this(p1, p2, false);
	}
	public Demo(int p1, String p2, boolean p3) {
		this(...);
	}
	...
	public Demo(int p1, String p2, boolean p3, .......) {
		this.p1 = p1;
		this.p2 = p2;
		...
	}
}

使用静态工厂方法可以通过命名区分不同的实例创建情况,可以一定程度上避免阅读性差、使用成本高的问题,但是解决不了传参过多的问题,同时也会造成一个难题,就是需要开发者需要想出比较好的、能够区分各种实例创建情况的名称

这种写法不利于扩展,阅读性也比较差,使用者只能通过一个个查看变量名称去判断怎么使用,同时,还有一个风险,那就是如果使用者传错了同种类型的参数,或者传错了同种类型的参数的顺序,编译器是不会发现的,但是会在程序运行时导致功能异常。

另外一种方式是使用JavaBeans的模式,也就是只提供一个不带参数的构造函数,然后通过setter一个个设置,提高了可阅读性,但是却不能保证对象信息在多线程情况下的同步,因为setter方法不具备原子性的。

这种情况下应该考虑使用Builder模式来代替。Builder模式应该都不陌生,这里简单提几个要点:
1.Builder类一般为宿主类的内部静态类,这样可以实现懒加载。
2.宿主类和Builder的数据传递就是Builder的对象,也就是说需要提供一个传参为该类的Builder类的对象的构造器。
3.Builder类的每个setter方法返回builder对象自己,同时需要增加校验操作,比如setMoney(float num)方法,当检查到传入的值<0时抛出IllegalArgumentException。
4.Builder模式也可以通过设计一个抽象的父类去约束项目中该父类子类的Builder规范以及提供一些子类通用的操作,而子类还可以继续扩充setter,看一下例子会更好懂:

public abstract class BaseClass {
	protected final List<String> baseList; //子类通用数据
	public abstract static class Builder<T extends Builder<T>> {
		protected List<String> baseList = new ArrayList();
		public T addItem(String item) {
			baseList.add(item);
			return childInstance();
		}
		protected abstract T childInstance();  //返回具体子builder实例
		public abstract BaseClass build(); 
	}
	public BaseClass(Builder<?> builder) {  //这里只能用?而不能用T,因为T是Builder的,使用?的话无论Builder的泛型是什么具体的Builder类都能传入
		baseList = builder.baseList;
	}
}
//子类的builder就可以不写addItem方法,同时可以继续扩展setter
public class ChildClass extends BaseClass {
	private int newParam;
	public static class Builder extends BaseClass.Builder<Builder> {
		private int newParam;
		public Builder() {}  //这里可以扩展传参,或者初始化某些变量
		@Override
		public Builder childInstance() {
			return this;
		}
		@Override
		public ChildClass build() {
			return new ChildClass(this);
		}
		public Builder setNewParam(int newParam) {
			this.newParam = newParam;
			return this;
		}
	}
	private ChildClass(Builder builder) {
		super(builder);
		newParam = builder.newParam;
	}
}
...
//用法
new ChildClass.Builder().addItem("test").setNewParam(23).build();

特别说明:
1.这里的Builder<T extends Builder<T>>的作用是确保传进来T必须是Builder的子类,如果Builder<T extends Builder<T>>换成Builder<T >的写法,完全可以随意传入一个非Builder的子类作为类型参数,比如有个类Demo,Builder<T >的写法可以传Demo作为T的泛型值,这样addItem的返回值就不是子Builder类了,Builder模式就错了,你就不能使用.addItem(…).setNewParam(…)了,假如是这种写法Builder<T extends Builder>,你可以传入另外一个也继承父Builder的子类,同样可能出错,而如果是Builder<T extends Builder<T>>的写法,传入的泛型值后生成的子Builder<T>不是父Builder<T>的子类型就会编译报错了,此时子Builder和父Builder的T一定是一致的。这种写法叫递归类型参数
2.内部静态类Builder和外部类BaseClass其实没有什么强联系,它们都可以作为独立的类去使用,所以在子类继承BaseClass时,它不一定要写一个内部静态类Builder(可以试一下)。
3.addItem方法的返回值可能会有人第一感官想返回一个T,T是泛型,是类级别的,addItem需要返回一个具体的对象。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值