设计模式之建造者模式

567 阅读6分钟

image.png

这是我参与8月更文挑战的第3天,活动详情查看:8月更文挑战

欢迎来到今天的学习,今天我们一起来唠唠建造者模式。多唠叨几句,前面我有提到本月将会对java的设计模式精讲,欢迎点击头像,关注我的专栏,我会持续更新,加油!

系列文章:

设计模式之单例模式

设计模式之工厂模式

....系列持续更新

话不多话,进入正题

建造者模式

建造者模式是另外一个高频使用的创建型设计模式——通常叫 Builder 模式,中文一般叫建造者模式或生成器模式。

今天我们改变下策略,先开始不讲原理,先从例子下手,从解决问题案例当中去寻求答案。你认为的那种答案。

先看建造者模式主要带来什么或者解决什么问题:

  • 直接使用构造函数或者使用 set 方法来创建对象方不方便?,有没有一种简单的方法创建对象

  • 存在的价值,为什么需要建造者模式创建对象

下面通过代码具体深入理解,我们只讲干货,让你看完就能解决现在你代码中的实际问题!

场景及案例分享

对同一种场景我们用建造者模式和不用建造者模式来进行代码对比:

很简单,我们创建学生对象


/**
 * 学生实体
 * 不用建造者模式
 */
public class Students {
    private String name;    //姓名
    private int age;        //年龄
    private String grade;   //班级
    private String gender;  //性别
    public Student(String name, int age, String grade, String gender) {
        this.name = name;
        this.age = age;
        this.grade = grade;
        this.gender = gender;
    }
    public Student(String name, int age, String grade) {
        this.name = name;
        this.age = age;
        this.grade = grade;
    }
    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }
   
}

 //上述代码写了三个不同参数的构造函数
public static void main(String[] args) {
    Student student01 = new Student("张三",26,"13班","男");
    
    Student student02 = new Student("张三",26,"13班");
    
    ...
}

上述有问题吗,没有问题。这代码可以吗,你觉得呢?问题显而易见(肯定还有很多小伙伴平常工作中都是这么搞的)。上面这段代码没有使用建造者模式,所以我们需要使用传统的 getter、setter 方法,并指定不同的入参来构造对象。如果参数过多,业务比较多,那需要创建太多构造方法。

而使用建造者模式后的类,功能却发生了完全不一样的变化,如下所示:


/**
 * 学生实体
 * 
 * 使用建造者模式创建对象
 */
public class Students {
    //所有属性
    private String name;   
    private int age;       
    private String grade;  
    private String gender; 
    
    public Students() {
    }
    
    //内部builder new对象
    public static Students builder() {
        return new Students();
    }
    
    //将属性作为步骤
    public Students name(String name) {
        this.name = name;
        return this;
    }
    //将属性作为步骤
    public Students age(int age) {
        this.age = age;
        return this;
    }
    //将属性作为步骤
    public Students grade(String grade) {
        this.grade = grade;
        return this;
    }
    //将属性作为步骤
    public Students gender(String gender) {
        this.gender = gender;
        return this;
    }
    
    //执行创建操作(最后封口return)
    public Students build() {
        validateObject(this);
        return this;
    }
    private void validateObject(Students Students) {
        //可以做基础预校验,或自定义校验
    }
    @Override
    public String toString() {
        return "Students{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", grade='" + grade + '\'' +
                ", gender='" + gender + '\'' +
                '}';
    }
}



 //用建造者模式来创建对象
public static void main(String[] args) {
    
    //创建对象
    Students st = Students.builder()
            .name("张三")
            .age("26")
            .grade("13班")
            .gender("男")
            .build();
            
      //创建对象
    Students st01 = Students.builder()
            .name("张三")
            .age("26")
            .grade("13班")
            .build();       
            
     //一步步建造一个个属性       
}

这,就是建造者模式来搞定对象。我们可以发现构建出了完全不同的对象实例,而使用传统的 getter、setter 方法,则需要写很多不同的构造函数来应对变化。

所以说,使用建造者模式能更方便地帮助我们按需进行对象的实例化,避免写很多不同参数的构造函数,同时还能解决同一类型参数只能写一个构造函数的弊端。

虽然上面案例的实现比较简单,但是也充分演示了如何使用建造者模式。在实际的使用中,通常可以直接使用 lombok 的 @Builder 注解实现类自身的建造者模式

为什么使用建造者模式来创建对象?存在的意义是什么

为什么要使用建造者模式来创建类?在我看来,有以下几点原因。

  • 第一,分步骤的属性赋值更适合多次运算结果类创建场景。在面向对象软件开发中,很多时候创建类所需要的参数并不是一次都能准备好的,比如,计算订单优惠价格、查询库存状态等,有的参数可能需要通过调用多个服务运算后才能得出,这时我们可以根据已知参数预先对类进行创建,等有合适的参数时再设置类的属性,而不是等到所有结果都齐备后才去创建类。

  • 第二,不需要关心特定类型的建造者的具体算法实现。 比如,我们在使用 StringBuilder 时,并不太关心它的具体代码实现,而是关心它提供给我们的使用功能。这在某些需要快速复用的场景中,能起到提升编码效率的作用。

  • 第三,提高代码的可阅读性,简洁明了,需要哪个功能(属性),就调用哪个,方便阅读,可维护性强。内部属性扩展性极强,自由地组合对象属性

  • 第四满足开闭原则,这也是设计模式的优点。 每一个建造者都相对独立,因此能方便地进行替换或新增,这就大大提升了代码的可扩展性。

当然,夸奖完,还是要指出一些存在一些客观性存在的问题。

  • 使用范围有限。 建造者模式所创建的对象一般都需要有很多的共同点,如果对象实例之间的差异性很大,则不适合使用建造者模式。

  • 容易造成超类,业务繁多,新的创建步骤就会被加进来,这会造成代码量的急剧膨胀,类会变得臃肿,最终形成一个庞大的超大类。

Netty当中的建造者模式

我们来看下Netty框架当中是怎么运用建造者模式的(Netty不懂的自行百度下,后续我会对Netty作出专题来讲)。

大家还记得,在服务端启动的时候有个启动辅助类ServerBootStrap,我们调用group方法、channel方法设置参数。这里面也使用了这种建造者链式编程来设置相关参数。


ServerBootstrap b = new ServerBootstrap();
//开始创建对象
b.group(bossGroup, workerGroup) //bossGroup和workerGroup 先不用管,本节主要讲建造者模式

            //赋值channel参数
            .channel(NioServerSocketChannel.class)
            //赋值option参数
            .option(ChannelOption.SO_BACKLOG, 100)
            //赋值handler参数
            .handler(new LoggingHandler(LogLevel.INFO))
            .childHandler(new ChannelInitializer<SocketChannel>() {
                @Override
                public void initChannel(SocketChannel ch) throws Exception {
                    System.out.println("服务启动");
                }
            });
    

下面我们来一起看下源码,源码我就直接从idea当中截图了,我会从中圈出重点:

先看group,设置完参数之后,便返回this对象

image.png

再看 childHandler,依然如此,是不是和我们上面学生的例子是一样的

image.png

其他几个参数也是一样,大家可以自行看下。

总结

OK,今天我们的建造者模式就讲到这里,感谢你的阅读,相信你能从中学到东西。同时也非常欢迎您的点赞,关注和分享!

我已经将本章收录在专题里,点击文章下方专题,关注专栏,我会每天发表干货,本月我会持续输入设计模式。我们下期再见!