一篇搞懂@Accessors与@Builder(结合项目经验)


前言

之前项目有用过@Accessors这个注解,但是没有很好实际的感触到这个注解的好处。前段时间实验室那的公司项目有个场景。让我切身体会到了,固来总结分享一下。

一、建造者模式

  • 在介绍这两个注解之前,先大概介绍一下建造者模式。

(一)、什么是建造者模式

建造者模式又叫创建者模式,是将一个复杂的对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。创建者模式隐藏了复杂对象的创建过程,它把复杂对象的创建过程加以抽象,通过子类继承或者重载的方式,动态的创建具有复合属性的对象

(二)、主要作用

在用户不知道对象的建造过程和细节的情况下就可以直接创建复杂的对象。

(三)、模式原理与uml类图

在这里插入图片描述

  • 产品类:一般是一个较为复杂的对象,也就是说创建对象的过程比较复杂,一般会有比较多的代码量。在本类图中,产品类是一个具体的类。

  • 抽象类。实际编程中,产品类可以是由一个抽象类与它的不同实现组成,也可以是由多个抽象类与他们的实现组成。

  • 抽象建造者:引入抽象建造者的目的,是为了将建造的具体过程交与它的子类来实现。这样更容易扩展。一般至少会有两个抽象方法,一个用来建造产品,一个是用来返回产品。

  • 建造者:实现抽象类的所有未实现的方法,具体来说一般是两项任务:组建产品;返回组建好的产品。

  • 导演类(指导者类):负责调用适当的建造者来组建产品,导演类一般不与产品类发生依赖关系,与导演类直接交互的是建造者类。一般来说,导演类被用来封装程序中易变的部分。

二、@Builder

(一)、为什么要使用Builder?

当一个类的成员变量比较多,同时,大部分的成员变量有默认值,仅有少量的成员变量需要初始化时,我们该如何设计这个类?我们需要让该类满足以下两个要求:

  • 方便用户创建复杂的对象(不需要知道实现过程),仅需要初始化必要的成员变量;
  • 代码复用性 & 封装性(将对象构建过程和细节进行封装 & 复用)
  • 例如:有一个类的成员变量很多,复杂。那么就会有很多种类的构造函数。这个时候就会需要一种方式能够动态的创建这个类

而这个时候这种设计场景就与建造者模式不谋而合。为此lombok就推出一款注解。@Builder进行快速开发。

(二)、@Builder流程分析

  • 编译前代码
@Data
@Builder
public class Student {
    
    
    String name;
    int age;
}

  • 反编译后主要代码

public class Student {
    
    
    String name;
    int age;

    Student(final String name, final int age) {
    
    
        this.name = name;
        this.age = age;
    }

    public static Student.StudentBuilder builder() {
    
    
        return new Student.StudentBuilder();
    }

    public String getName() {
    
    
        return this.name;
    }

    public int getAge() {
    
    
        return this.age;
    }

    public void setName(final String name) {
    
    
        this.name = name;
    }

    public void setAge(final int age) {
    
    
        this.age = age;
    }
    public String toString() {
    
    
        return "Student(name=" + this.getName() + ", age=" + this.getAge() + ")";
    }
    public static class StudentBuilder {
    
    
        private String name;
        private int age;

        StudentBuilder() {
    
    
        }

        public Student.StudentBuilder name(final String name) {
    
    
            this.name = name;
            return this;
        }

        public Student.StudentBuilder age(final int age) {
    
    
            this.age = age;
            return this;
        }

        public Student build() {
    
    
            return new Student(this.name, this.age);
        }

        public String toString() {
    
    
            return "Student.StudentBuilder(name=" + this.name + ", age=" + this.age + ")";
        }
    }
}
  • 测试代码:
    @Test
    public void testBuilder(){
    
    
        Student student = Student.builder().name("18").age(19).build();
        System.out.println(student.toString());

    }

-由此可以看出@Builder下的使用方式主要是通过以下几个方法,实现分步构造:

 public static Student.StudentBuilder builder() {
    
    
        return new Student.StudentBuilder();
    }
public static class StudentBuilder {
    
    
        private String name;
        private int age;

        StudentBuilder() {
    
    
        }

        public Student.StudentBuilder name(final String name) {
    
    
            this.name = name;
            return this;
        }

        public Student.StudentBuilder age(final int age) {
    
    
            this.age = age;
            return this;
        }

        public Student build() {
    
    
            return new Student(this.name, this.age);
        }
    }
  • 通过类方法builder(),创建一个StudentBuilder()的类。然后这个类通过返回自身(this) 的链式编程可以动态的构造对象 最后通过build构造出一个新的Student的对象,实现了构造过程与展现最后对象的解耦合。而在这个过程中builder()就是抽象建造者,它决定了由哪个实际的builder类(StudentBuilder)去创建它而在完成对创建顺序的控制,完成创建过程的分离这个过程的也就是指挥者的功能。如
  • Student.builder().name("18").age(19).build();
  • 但是通过对反编译的代码进行简单的分析后,很明显的会发现会有两个问题:
    一、在整个过程中会多创建个中间类StudentBuilder实现解耦合多占用了内存。
    二、由于bulider()方法是类方法,所以在进行创建过第一次后,不能对创建后的对象继续进行动态赋值。
  • 由此可以选择lombok推出的另一款注解@Accessors。

三、@Accessors

(一)、@Accessors流程分析

编译前代码:

@Data
@Accessors(chain = true)
public class Student {
    
    
    String name;
    int age;
}
  • 反编译后的主要代码:
public class Student {
    
    
    String name;
    int age;

    public Student() {
    
    
    }

    public String getName() {
    
    
        return this.name;
    }

    public int getAge() {
    
    
        return this.age;
    }

    public Student setName(final String name) {
    
    
        this.name = name;
        return this;
    }

    public Student setAge(final int age) {
    
    
        this.age = age;
        return this;
    }
}

  • 测试代码
@Test
    public void testBuilder(){
    
    
        Student student = new Student().setAge(18).setName("chen");
        System.out.println(student.toString());

    }
  • 可以看到通过设置chain = true 则开启了链式构造,在自身赋值的时候,返回自身对象。避免了多余bean的创建,以及可以继续通过自身进行链式赋值。

(二)、@Accessors其他字段使用

  • 使用prefix属性,getter和setter方法会忽视属性名的指定前缀(遵守驼峰命名)。

  • 编译前

@Data
@Accessors(prefix = "s")
public class Student {
    
    
    String sName;
    int sAge;
}

  • 反编译后
public class Student {
    
    
    String sName;
    int sAge;

    public Student() {
    
    
    }

    public String getName() {
    
    
        return this.sName;
    }

    public int getAge() {
    
    
        return this.sAge;
    }

    public void setName(final String sName) {
    
    
        this.sName = sName;
    }

    public void setAge(final int sAge) {
    
    
        this.sAge = sAge;
    }

}
  • 使用fluent属性,getter和setter方法的方法名都是属性名,且setter方法返回当前对象.
  • 编译前
@Data
@Accessors(fluent = true)
public class Student {
    
    
    String sName;
    int sAge;
}

  • 反编译后
public class Student {
    
    
    String sName;
    int sAge;

    public Student() {
    
    
    }

    public String sName() {
    
    
        return this.sName;
    }

    public int sAge() {
    
    
        return this.sAge;
    }

    public Student sName(final String sName) {
    
    
        this.sName = sName;
        return this;
    }

    public Student sAge(final int sAge) {
    
    
        this.sAge = sAge;
        return this;
    }
}

四、项目收获杂谈

  • 背景:前端时间做了一个物流平台的项目。其中我负责一个调度单模块。他的接口获取到的元素赋值到dto实体的字段还是挺多的。当初就选用了@Accessors来链式赋值对象。事实证明是对的,因为真实项目不像网上的项目那样很多都是逻辑已经定好的。而是随时会变需求,或者变数据结构。如果那种调接口还是接受很多的参数还用那种构造函数。那么首先一是可读性会很差。再者是后面如果要修改的时候会很麻烦。违反了修改开闭原则
  • 这是一个真实的案例:公司那边有一个接口是获取当前在途调度单。因为在途的话没有收货与提货重量。但是公司那的接口有个收获数据,可能给的是假数据。一开始没在意到这个,以为有都啥就都填啥了。知道后面接口报错后才知道这个逻辑。如果一开始使用的是构造器赋值,那么修改起来会很麻烦。很容易出错,因为对应了某个特定的构造器。而用了链式构造后。只要把那个不存在的.setXXX删掉即可。大大减少了开发时间。
    如:
    在这里插入图片描述

总结

介绍分析了两个@Accessors与@Builder的差别。
总之就是:两者都可以实现链式构造结合。如果想要实战方便些可以使用@Accessors,如果想要更贴合建造者模式进行解耦合的话可以使用@Builder。

ps:如果一定要使用@Builder进行对象的第二次以后的继续链式赋值的话可以结合toBuild=true使用即@Builder(toBuilder = true)。

@Test
    public void testBuilder(){
    
    
        Student student = Student.builder().sAge(19).sName("chen").build();
        student.toBuilder().sAge(20);
    }

码字不易,如有收获,敬请点赞~
如有不足敬请指正!

猜你喜欢

转载自blog.csdn.net/weixin_45938441/article/details/121116819