设计模式-构造器模式

构造器模式:

构造器模式就是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的这个源码,里面的很多类似的地方,其实它们用到的,都是构造器的这个模式。

在电商项目里面,对于这个设计模式,也绝对是有很多的使用场景,举个例子,比如说 销售出库单 之类的,这种很复杂的什么什么单的构建,它里面的字段可能特别多,然后,构建的这个过程,这个逻辑可能就很复杂,像这种对象的构建,完全适合采用这个构造器模式。

end

posted @ 2022-10-06 12:04  HarryVan  阅读(35)  评论(0编辑  收藏  举报