设计模式-构造器模式
构造器模式:
构造器模式就是builder模式,用来封装复杂对象的构造逻辑。。
builder模式所要实现的场景如下:
比如说现在要构造一个复杂的对象,然后这个对象有很多的属性,有些属性构造的时候,需要做,比如一些校验,一些格式转换,或者会有其他的一些等逻辑等等,大概就是这样的一个场景。
没有用构造器模式的Demo:
然后,先来做 一个没有用模式的Demo,创建一个WithoutBuilderPatternDemo类,里面有一个main方法,和一个静态内部类public static class Product{}这个类,假设Product类,它是很复杂的一个对象,有3个字段,filed1,filed2,filed3,ok,然后有一堆的这个get、set方法,再加一个toString方法。
那现在就可以来,开始构造这个复杂的Product对象,比如说,我们可能会这样来做,在main方法中,先new一个Product对象,Product product = new Product();,
然后,设置field1属性,我们这边可能要,假设,在设置field1之前进行较为复杂的这个校验逻辑,System.out.println("在设置field1之前进行较为复杂的校验逻辑");,接下来,再把校验后的,比如说 值1,set到Product对象中的field1中,product.setField1("值1");,
然后,设置field2属性,在设置field2之前进行复杂的数据格式转换逻辑,System.out.println("在设置field2之前进行复杂的数据格式转换逻辑");,再把格式转换后的,比如说值2,set到Product对象中的field2中,product.setField2("值2");,
然后,是设置field3属性,在设置field3之前进行复杂的数据处理逻辑,跟其他的对象进行关联,比如干了这么一件事情,System.out.println("在设置field3之前进行复杂的数据处理逻辑,跟其他对象的数据进行关联");,再把数据处理后的,比如说值3,set到Product对象中的field3中,product.setField3("值3");,
最后,把product对象输出一下,执行看一下输出结果,System.out.println(product);。
没有用构造器模式的代码如下:
public class WithoutBuilderPatternDemo { public static void main(String[] args) { // 构造这个复杂的product对象 Product product = new Product(); // 设置field1属性 System.out.println("在设置field1之前进行复杂的校验逻辑"); product.setField1("值1"); // 设置field2属性 System.out.println("在设置field2之前进行复杂的数据格式转化逻辑"); product.setField2("值2"); // 设置field3属性 System.out.println("在设置field3之前进行复杂的数据处理逻辑,跟其他对象的数据进行关联"); product.setField3("值3"); System.out.println(product); // 上面是简化的一个逻辑,实际上对于一些有几十个字段,甚至是上百个字段的复杂对象的构建 // 上面那段代码会极度膨胀,非常复杂 // 一个是说,大量代码堆积在一起,维护性非常差,可读性非常差,一坨代码,跟屎一样,读不懂,没法改 // 另外一个,就是说,这段逻辑,如果在多个地方都有使用的话,一旦这段逻辑出现了一些编号,那么可能就需要 // 在多个地方修改这一大坨跟屎一样的代码 // 这时,我们就需要把不同的构造的步骤,抽取成某一个方法分别构造 } public static class Product { private String field1; private String field2; private String field3; public String getField1() { return field1; } public void setField1(String field1) { this.field1 = field1; } public String getField2() { return field2; } public void setField2(String field2) { this.field2 = field2; } public String getField3() { return field3; } public void setField3(String field3) { this.field3 = field3; } @Override public String toString() { return "Product [field1=" + field1 + ", field2=" + field2 + ", field3=" + field3 + "]"; } } }
那么,最终它这里 可能就是会变成这样子,就是构造成一个复杂的对象,每构造一个field,可能都会在这之前有一些逻辑。当然,其实上面是简化的一个逻辑,实际上,对于一些,有几十个字段,甚至上百个字段的复杂对象的构建,上面那段代码会极度膨胀,非常的复杂。那这样的坏处,一个是说,大量代码堆积在一起,维护性非常差,可读性非常差,就是一坨代码,到最后跟屎一样,读不懂,没法改,可能这个代码,它就是这样子。那另外一个,这段逻辑 它如果是在 多个地方都有使用的话,那么,一旦这段逻辑出现了一些变化,可能就需要 在多个地方 修改这一坨跟屎一样的代码。
当然了,其实可以把所谓的这个构造的过程,拿到一个工厂里面去,但是在这个工厂里面,它其实也是一样的。在这个工厂里面,它可能会混杂着大量的代码,当然你说,我可以把 不同的这个构造的步骤 抽取成某一个方法,当然你可以这么去做,但是这样做的话,实际上来说就是,你还是没有用到 这个构造器模式。
构造器模式,它其实体现的是说,你的整个这个,比较复杂的一个构建逻辑,它可能需要有很多的逻辑去构建。而并不是说,体现在 可以把一大堆很复杂的这个逻辑,放在一个工厂里面去,在这个工厂 把多个步骤 给抽成不同的方法,你也可以这么去做,但是那样的话,同样会导致,就是同样会有一个问题。什么问题啊,就是构造模式和把工厂模式用到极致的一个思维上的对比。那先看下构造器模式的Demo案例,再来对比它们的区别。
构造器模式的Demo案例:
再创建一个BuilderPatternDemo类,同样的逻辑,如果采用构造器模式,大概看起来,代码应该是什么样子的。首先的话,它得有一个Builder这么一个接口,然后的话,它这里,就会有一些用来构建的这个方法,void field1(String value); void field2(String value); void field3(String value);,然后,还得有一个用于返回Product实例的方法,比如就叫create();,那同样的也有一个包含field1,field2,field3,以及get、set方法和toString方法的Product的实例类,这样的,这个是一个Builder接口。
然后再有一个的话就是,实际的这个Builder的实现类,它这个里面,要有一个Product的实例,先给它new一个Product出来,然后分别在每个用来构建的方法中,去实现具体的构建逻辑,并把构建的结果set到这个Product实例的对应的field字段中,最后再在create方法中去返回这个Product的实例。
这样相当于就是,通过这个Builder,先定义一个包含构造步骤的接口,然后的话,又定义了一个Builder的具体的实现类,在这个实现类里面,实现了它的构建的每一个步骤,然后,把这个比较复杂的构建逻辑,给它拆到了一个一个方法里面去,这样的话,就把整个逻辑给拆的非常的清晰。
然后,还可以有一个叫做Director的指导类,这个指导类里面会有一个Builder的引用,private Builder builder;,和一个包含Builder类型参数的Director构造方法,public Dierctor(Builder builder){ this.builder = builder;},以及一个返回值为Product对象类型的build方法,可以认为它这边就是builder出来Product这么一个东西。当然,这个builder方法可以对应Product中需要构建的属性,传入相应个数的参数,比如 field1,field2,field3,public Product builder(String field1, String field2, String field3) { },可以采取这样的方式,然后 通过调用builder中相对应的构建方法,对每个参数分别进行构建,builder.field1(field1);builder.field2(field2);builder.field3(field3);,最后,return一个builder.create();,把构建好的Product实例返回。
那以这样的一个模式,以这个构造器模式来做的话,应该怎么构建,也可以通过一个main方法去演示,main方法中有一个Director的实例,先new一个Director出来,并传入一个具体的Builder实例,new一个ConcreteBuilder作为Director的参数传入,Director director = new Director(new ConcreateBuilder);,然后,通过Director中的build方法,传入构建对象Product所需要构建的对应的参数,交给Builder分别进行构建,并把构建好的构建对象Product返回回来,Product product = director.build("值1","值2","值3");,最后,把product输出一下,执行看一下构建结果,System.out.println(product);。
构造器模式Demo的代码如下:
public class BuilderPatternDemo { public static void main(String[] args) { Director director = new Director(new ConcreteBuilder()); Product product = director.builder("值1","值2","值3"); System.out.println(product); // 好处1:通过builder接口将复杂构建步骤拆分成了多个部分,代码逻辑清晰,维护性和扩展性都很好 // 好处2:将对象构建的过程,封装在了director里面,director来基于builder进行构建,构建逻辑修改,不需要修改很多地方 // 好处3:相对于工厂,有一个很好的抽象设计,director和builder } public static class Product { private String field1; private String field2; private String field3; public String getField1() { return field1; } public void setField1(String field1) { this.field1 = field1; } public String getField2() { return field2; } public void setField2(String field2) { this.field2 = field2; } public String getField3() { return field3; } public void setField3(String field3) { this.field3 = field3; } @Override public String toString() { return "Product [field1=" + field1 + ", field2=" + field2 + ", field3=" + field3 + "]"; } } public interface Builder { void field1(String value); void field2(String value); void field3(String value); Product create(); } public static class ConcreteBuilder implements Builder { private Product product = new Product(); public void field1(String value) { System.out.println("在设置field1之前进行复杂的校验逻辑"); product.setField1(value); } public void field2(String value) { System.out.println("在设置field2之前进行复杂的数据格式转化逻辑"); product.setField2(value); } public void field3(String value) { System.out.println("在设置field3之前进行复杂的数据处理逻辑,跟其他对象的数据进行关联"); product.setField3(value); } public Product create() { return product; } } /** * director是面向builder的接口,来编程的 * director可以负责控制构建的一个步骤,具体的每个步骤的逻辑封装在具体的builder类中 * 如果我们此时要更换一整套的构建步骤,可以再搞一个新的builder类就可以了 * 但是我们的整个构建步骤是没有任何改变的 * * 如果整个构建步骤变化了,但是对构建的逻辑是没有影响的 */ public static class Director { private Builder builder; public Director (Builder builder) { this.builder = builder; } public Product builder(String field1, String field2, String field3) { builder.field1(field1); builder.field2(field2); builder.field3(field3); return builder.create(); } } }
构建结果如下:
在设置field1之前进行复杂的校验逻辑 在设置field2之前进行复杂的数据格式转化逻辑 在设置field3之前进行复杂的数据处理逻辑,跟其他对象的数据进行关联 Product [field1=值1, field2=值2, field3=值3] Process finished with exit code 0
ok,当director.build("值1","值2","值3");,这边一构建,Director这个里面就会走build这个方法,它其实就定义了,整个构建的一个步骤。
然后,在Builder里面,它其实主要是,把那个构建的多个步骤给它拆开了,拆开以后,这个director决定说 这个Builder,应该按照怎么样的一个过程,来构建出来这个Product对象。那实际上的话,就是这个Builder,它的构建的这个步骤,就会按照Director中定义的顺序,一个一个去执行。最后,build好了以后,就会生成出来Product这么一个东西了。
使用构造器模式,它的好处有3个:
好处一 就是,通过这个Builder接口,可以将这个复杂的构建步骤 拆分成了多个部分,代码逻辑非常清晰,维护性和扩展性都很好。
好处2 就是,将对象构建的这个过程 封装在了,这个所谓的Director里面,由这个Director来基于Builder进行构建,这样的话,如果那个构建逻辑 修改了,就不需要 修改很多地方。
好处3 就是,相对于,如果你只是用一个工厂,用工厂来集中 所有的构建逻辑,然后 把每一个构建步骤 拆分成多个方法的话,相对于工厂,我们这个构造器模式,有一个很好的抽象设计,就是有Director 和 这个builder的,这么一个概念。这个Director其实是面向Builder的这么一个接口 来编程的,Director可以用来控制 构建的一个步骤,然后 具体的每个步骤的逻辑,封装在具体的这个Builder类中,如果 我们此时要更换 一整套的构建逻辑,那我们可以 再搞一个全新的Builder类,就是搞另外一个Builder的具体的实现类ConcreteBuilder,在这个里面的构建逻辑给它修改一下,然后,把新的Builder类注入到Director类中,就可以了,而我们整个的Director,这个注入Builder的过程 和 构建的步骤 是没有任何改变的。然后另外一点的话,如果这个构建的步骤 有变化了,但是,对这个构建的逻辑是没有影响的,所以说,它其实起到了这样的一个作用,把构建对象的步骤,和构建对象的逻辑,进行了分离解耦。
使用工厂模式和构造器模式的区别:
当然,如果硬是用工厂也可以实现,但是你会发现说,那个工厂做到最后,然后那个工厂的,整个类与类之间的设计,就是 跟这个构造器模式里面的Director和这个Builder,可能就是一样了。所以这个模式与模式之间,可能是有一些相似之处的,但是不同的两个模式之间,它一定是有一些细微的差别,它体现的差别,可能是在整个设计思想上的一个差别。ok,所以说整个构造的话,就是这样的。只要记住,如果你要构造一个复杂的对象,就可以采取构造器的模式来做。
优化以后的构造器模式:
最后,还要说一个,就是优化以后的构造器模式,再写一个类,叫做OptimizedBuilderPatternDemo,这个的话,可以把BuilderPatternDemo中的除了main方法和Director指导类,其他它的Product对象类,Builder接口,和Builder的实现类ConcreteBuilder,这些都可以拷贝,就是Director现在就不要了。然后,它在Builder接口中定义的方法,每构造一步返回的都是Builder,而不再是void。在Builder实现类ConcreteBuilder中,每个构建方法的返回值,直接就给它返回一个this,这个Builder对象本身。那么,这个构造的逻辑就交给我们来决定了,怎么来决定呢,先new出这个ConcreteBuilder的实例,然后,通过调用自身方法的方式,调用每一个构造逻辑,最后调用create方法将构造完成的Product对象返回。它可能会变成这个样子,
Product product = new ConcreteBuilder().filed1("值1").filed2("值2").filed3("值3").create();
就是这个代码最终可能会变这个样子,ok,然后,我们再System.out.println(product);,输出这个构建好的对象。
优化以后的构造器模式的代码如下:
public class OptimizedBuilderPatternDemo { public static void main(String[] args) { Product product = new ConcreteBuilder() .field1("值1") .field2("值2") .field3("值3") .create(); System.out.println(product); } public static class Product { private String field1; private String field2; private String field3; public String getField1() { return field1; } public void setField1(String field1) { this.field1 = field1; } public String getField2() { return field2; } public void setField2(String field2) { this.field2 = field2; } public String getField3() { return field3; } public void setField3(String field3) { this.field3 = field3; } @Override public String toString() { return "Product [field1=" + field1 + ", field2=" + field2 + ", field3=" + field3 + "]"; } } public interface Builder { Builder field1(String value); Builder field2(String value); Builder field3(String value); Product create(); } public static class ConcreteBuilder implements Builder { private Product product = new Product(); public Builder field1(String value) { System.out.println("在设置field1之前进行复杂的校验逻辑"); product.setField1(value); return this; } public Builder field2(String value) { System.out.println("在设置field2之前进行复杂的数据格式转化逻辑"); product.setField2(value); return this; } public Builder field3(String value) { System.out.println("在设置field3之前进行复杂的数据处理逻辑,跟其他对象的数据进行关联"); product.setField3(value); return this; } public Product create() { return product; } } }
然后执行来看一下,这个输出其实是一样的。就是说我们通过这样的一个优化,就把整个Director给它干掉了,然后把整个复杂对象的一个构建的过程,交给了我们这个调用端,这个调用端可以自己去选择,按照什么样的一个步骤,给它设置什么值,去做这个构造。
构造器模式的应用:
实际上,现在基本上流行的一些开源框架,对于构造器模式的应用,一般都是上面OptimizedBuilderPatternDemo,这种变种模式的。比如,单元测试框架里面的那个Mockito框架,when().thenReturn()什么什么东西,它会完成一个模拟对象的构建;包括那个Spring test里的MvcMock这个对象,它也是执行了什么get().thenExpected(),然后什么什么东西,完成了一个mvc测试对象的构建;包括如果去读springboot的这个源码,里面的很多类似的地方,其实它们用到的,都是构造器的这个模式。
在电商项目里面,对于这个设计模式,也绝对是有很多的使用场景,举个例子,比如说 销售出库单 之类的,这种很复杂的什么什么单的构建,它里面的字段可能特别多,然后,构建的这个过程,这个逻辑可能就很复杂,像这种对象的构建,完全适合采用这个构造器模式。