<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          同事寫了一個瘋狂的類構(gòu)造器,我要瘋了,Builder 模式都不會么???!

          共 21309字,需瀏覽 43分鐘

           ·

          2021-04-28 15:05

          點擊關(guān)注公眾號,Java干貨及時送達

          瘋狂的類構(gòu)造器

          最近棧長在做 Code Review 時,發(fā)現(xiàn)一段創(chuàng)建對象的方法:

          Task task = new Task(112, "緊急任務(wù)""處理一下這個任務(wù)"
            90, 3, 1, 36, "劉主管", 18, "客服1""11, 12, 13"
            "客服3, 客服4, 客服5"true, new Date(), 
            new Date(), new Date(), new Date(), new Date(), 
            0, "需要盡快完成", ...);

          真實代碼敏感性,上面的代碼僅為模仿,實際要比這個更長、更復(fù)雜……

          當我看到那段代碼時,我簡直要瘋了??!

          拖了半天才看完,到處充滿著魔法值不說,把一個類所有參數(shù)都放在一個構(gòu)造器里面,這個構(gòu)造器也太瘋狂了……這種寫法也實在太 low 了!

          在實際開發(fā)過程中,棧長經(jīng)常看到同事們這樣的寫法,比上面的更長的構(gòu)造器你見過沒?我反正見過!

          一方面,也許他們真不知道怎么寫才更好,畢竟經(jīng)驗有限,這個可以原諒。但另一方面,也許他們就是為了偷懶,或者為了趕時間,反正這都是對自己和同事不負責(zé)的表現(xiàn)。

          如果你在公司看到同事寫這樣的優(yōu)秀代碼,請把這篇文章發(fā)給他。

          看看大量參數(shù)構(gòu)造器的缺點:

          • 參數(shù)過多時,代碼太長,極不優(yōu)雅,維護極難;
          • 不能判斷出哪些是必須參數(shù),哪些是可選參數(shù),可選參數(shù)也得給個默認值;
          • 分不清變量值對應(yīng)哪個變量,如順序?qū)?yīng)錯,很容易造成錯誤;
          • 構(gòu)造器參數(shù)增減時,會影響所有創(chuàng)建該對象的地方,影響擴展性;

          構(gòu)造器正確的用法是只給出幾個必選、重要參數(shù)的構(gòu)造器,而不是把所有參數(shù)放在一個構(gòu)造器中。

          比如我們看下 JDK 線程池的構(gòu)造器用法:

          線程池就把幾個重要的參數(shù)封裝成了幾個構(gòu)造器,這樣用戶就可以根據(jù)實際需要調(diào)用具體的某個構(gòu)造器。

          基本 SET 方法改良

          再回到同事寫的那個代碼,寫出那樣長的構(gòu)造器,我真的服了。

          最基本也得寫成這樣吧:

          Task task = new Task();
          task.setId(112);
          task.setName("緊急任務(wù)");
          task.setContent("處理一下這個任務(wù)");
          task.setParentId(90);
          task.setType(3);
          task.setLevel(1);
          task.setAssignFromId(36);
          task.setAssignFromName("劉主管");
          task.setAssignTo(18);
          task.setAssignTo("客服1");
          task.setCandidateId("11, 12, 13");
          task.setCandidateName("客服3, 客服4, 客服5");
          task.isSendEmail(true);
          task.setCreateTime(new Date());
          task.setBeginTime(new Date());
          task.setEndTime(new Date());
          task.setFinishTime(new Date());
          task.setUpdateTime(new Date());
          task.setStatus(0);
          task.setMemo("需要盡快完成");
          // ...

          這個創(chuàng)建對象的方式是最普通不過了,也是用的最多的了。

          這種寫法雖然看起來很直觀,但是有以下幾個缺點:

          • 參數(shù)多的情況下 setter 非常多,代碼非常長,不是很優(yōu)雅;
          • 不能判斷出哪些是必須參數(shù),哪些是可選參數(shù);
          • 容易漏掉一些參數(shù),并且很難檢查出來;
          • 容易搞錯對象名,造成潛在錯誤,很難排查(如:同時有 user 和 user2,在 user 賦值過程中錯誤的復(fù)制了 user2 對象);

          Builder 模式改良

          下面棧長教大家用 Builder 模式改良下,下面是改良后的代碼:

          package cn.javastack.test.designpattern.builder;

          import java.util.Date;

          /**
           * @author: 棧長
           * @from: 公眾號Java技術(shù)棧
           */
          public class Task {

              private long id;
              private String name;
              private String content;
              private int type;
              private int status;
              private Date finishDate;

              private Task(TaskBuilder taskBuilder) {
                  this.id = taskBuilder.id;
                  this.name = taskBuilder.name;
                  this.content = taskBuilder.content;
                  this.type = taskBuilder.type;
                  this.status = taskBuilder.status;
                  this.finishDate = taskBuilder.finishDate;
              }

              /**
               * @author: 棧長
               * @from: 公眾號Java技術(shù)棧
               */
              public static class TaskBuilder {

                  private long id;
                  private String name;
                  private String content;
                  private int type;
                  private int status;
                  private Date finishDate;

                  public TaskBuilder(long id, String name) {
                      this.id = id;
                      this.name = name;
                  }

                  public TaskBuilder content(String content) {
                      this.content = content;
                      return this;
                  }

                  public TaskBuilder type(int type) {
                      this.type = type;
                      return this;
                  }

                  public TaskBuilder status(int status) {
                      this.status = status;
                      return this;
                  }

                  public TaskBuilder finishDate(Date finishDate) {
                      this.finishDate = finishDate;
                      return this;
                  }

                  public Task build(){
                      return new Task(this);
                  }

              }

              public long getId() {
                  return id;
              }

              public String getName() {
                  return name;
              }

              public String getContent() {
                  return content;
              }

              public int getType() {
                  return type;
              }

              public int getStatus() {
                  return status;
              }

              public Date getFinishDate() {
                  return finishDate;
              }

              @Override
              public String toString() {
                  return "Task{" +
                          "id=" + id +
                          ", name='" + name + '\'' +
                          ", content='
          " + content + '\'' +
                          "
          type=" + type +
                          "
          , status=" + status +
                          "
          , finishDate=" + finishDate +
                          '}';
              }

          }

          說下簡單思路:

          1)在 Bean 類里面新建一個靜態(tài)內(nèi)部類:XxxBuilder;

          2)把 Bean 類所有參數(shù)復(fù)制到 XxxBuilder,然后在 XxxBuilder 新建必須參數(shù)的構(gòu)造器,其他參數(shù)使用變量名作為方法然后返回自身(this)以便形成鏈式調(diào)用;

          3)在 Bean 類里面新建一個接收 XxxBuilder 參數(shù)的私有構(gòu)造器,避免使用 new 創(chuàng)建對象;

          4)在 XxxBuilder 類新建一個 build 方法開始構(gòu)建 Bean 類,也是作為鏈式調(diào)用的結(jié)束;

          使用方法:

          使用方式如下,先創(chuàng)建構(gòu)造器,然后在每個方法后使用 . 帶出所有方法,一目了然,最后調(diào)用 build 方法以結(jié)束鏈式調(diào)用創(chuàng)建 bean。

          參考代碼如下:

          /**
           * @author: 棧長
           * @from: 公眾號Java技術(shù)棧
           */
          private static void testBuilder() {
              Task task = new Task.TaskBuilder(99, "緊急任務(wù)")
                      .type(1)
                      .content("處理一下這個任務(wù)")
                      .status(0)
                      .finishDate(new Date())
                      .build();
              System.out.println(task);
          }

          結(jié)果輸出:

          Task{id=99, name='緊急任務(wù)', content='處理一下這個任務(wù)', type=1, status=0, finishDate=...

          Builder 模式的優(yōu)點:

          • 鏈式調(diào)用,優(yōu)雅、清晰、一目了然;
          • 一行代碼完成對象創(chuàng)建,避免了多行代碼賦值過程出錯;
          • 省去了大量冗余變量,避免變量復(fù)制出錯;

          Builder 模式的缺點:

          • 需要冗余的 Builder 類,以及大量相等重復(fù)的成員變量,大大增加了代碼量,維護難度相對較大;
          • 只適合一次賦值創(chuàng)建對象,多次賦值的場景還需要新增 set 方法配合,不是很靈活;

          Lombok 實現(xiàn) Builder 模式

          常規(guī)的 Builder 模式需要新增大量的代碼,維護難度比較大,這里棧長再介紹一下 Lombok 中的 Builder 模式,一個 @Builder 注解搞定所有,輕松維護。

          用 Lombok 改良后的代碼如下:

          /**
           * @author: 棧長
           * @from: 公眾號Java技術(shù)棧
           */
          @Builder
          public class LombokTask {

              private long id;
              private String name;
              private String content;
              private int type;
              private int status;
              private Date finishDate;

          }

          我還能說什么?兩個字:真香!

          再來看下怎么使用:

          /**
           * @author: 棧長
           * @from: 公眾號Java技術(shù)棧
           */
          private static void testLombokBuilder() {
              LombokTask lombokTask = new LombokTask.LombokTaskBuilder()
                .id(99)
                      .name("緊急任務(wù)")
                      .type(1)
                      .content("處理一下這個任務(wù)")
                      .status(0)
                      .finishDate(new Date())
                      .build();
              System.out.println(lombokTask);
          }

          或者 new 都不要了,直接調(diào)用靜態(tài)方法:

          /**
           * @author: 棧長
           * @from: 公眾號Java技術(shù)棧
           */
          private static void testLombokBuilder2() {
              LombokTask lombokTask = LombokTask.builder()
                      .id(99)
                      .name("緊急任務(wù)")
                      .type(1)
                      .content("處理一下這個任務(wù)")
                      .status(0)
                      .finishDate(new Date())
                      .build();
              System.out.println(lombokTask);
          }

          接下來我們來看下這個 @Builder 注解到底做了什么:

          public class LombokTask {
              private long id;
              private String name;
              private String content;
              private int type;
              private int status;
              private Date finishDate;

              LombokTask(final long id, final String name, final String content, final int type, final int status, final Date finishDate) {
                  this.id = id;
                  this.name = name;
                  this.content = content;
                  this.type = type;
                  this.status = status;
                  this.finishDate = finishDate;
              }

              public static LombokTask.LombokTaskBuilder builder() {
                  return new LombokTask.LombokTaskBuilder();
              }

              public static class LombokTaskBuilder {
                  private long id;
                  private String name;
                  private String content;
                  private int type;
                  private int status;
                  private Date finishDate;

                  LombokTaskBuilder() {
                  }

                  public LombokTask.LombokTaskBuilder id(final long id) {
                      this.id = id;
                      return this;
                  }

                  public LombokTask.LombokTaskBuilder name(final String name) {
                      this.name = name;
                      return this;
                  }

                  public LombokTask.LombokTaskBuilder content(final String content) {
                      this.content = content;
                      return this;
                  }

                  public LombokTask.LombokTaskBuilder type(final int type) {
                      this.type = type;
                      return this;
                  }

                  public LombokTask.LombokTaskBuilder status(final int status) {
                      this.status = status;
                      return this;
                  }

                  public LombokTask.LombokTaskBuilder finishDate(final Date finishDate) {
                      this.finishDate = finishDate;
                      return this;
                  }

                  public LombokTask build() {
                      return new LombokTask(this.id, this.name, this.content, this.type, this.status, this.finishDate);
                  }

                  public String toString() {
                      return "LombokTask.LombokTaskBuilder(id=" + this.id + ", name=" + this.name + ", content=" + this.content + ", type=" + this.type + ", status=" + this.status + ", finishDate=" + this.finishDate + ")";
                  }
              }
          }

          這是反編譯后的代碼,可以看出來邏輯都是一樣的。

          Lombok 還可以添加各種類構(gòu)造器、toString 等系列注解,幾個注解完全可以達到想要的效果,但代碼量和可維護性是天壤之別。

          很多人不建議使用 Lombok,仁者見仁,智者見智,這里不再討論,相關(guān)話題可以閱讀我之前寫的文章:

          使用 Lombok 帶來了很多便利,不用多說,是真的香,東西是好東西,就是要團隊規(guī)范一起使用,避免踩坑。更多工具系列使用文章請關(guān)注公眾號Java技術(shù)棧,在菜單中閱讀。

          Java 8 實現(xiàn) Builder 模式

          Java 8 帶來了函數(shù)式接口編程,所以在 Java 8 中可以一個實現(xiàn)通用的 Builder:

          public class GenericBuilder<T{

              private final Supplier<T> instantiator;

              private List<Consumer<T>> instanceModifiers = new ArrayList<>();

              public GenericBuilder(Supplier<T> instantiator) {
                  this.instantiator = instantiator;
              }

              public static <T> GenericBuilder<T> of(Supplier<T> instantiator) {
                  return new GenericBuilder<T>(instantiator);
              }

              public <U> GenericBuilder<T> with(BiConsumer<T, U> consumer, U value) {
                  Consumer<T> c = instance -> consumer.accept(instance, value);
                  instanceModifiers.add(c);
                  return this;
              }

              public T build() {
                  T value = instantiator.get();
                  instanceModifiers.forEach(modifier -> modifier.accept(value));
                  instanceModifiers.clear();
                  return value;
              }
          }

          參考:

          http://www.ciphermagic.cn/java8-builder.html

          使用方式:

          /**
           * @author: 棧長
           * @from: 公眾號Java技術(shù)棧
           */
          private static void testJava8Builder() {
              Java8Task java8Task = GenericBuilder.of(Java8Task::new)
                      .with(Java8Task::setId, 99L)
                      .with(Java8Task::setName, "緊急任務(wù)")
                      .with(Java8Task::setType, 1)
                      .with(Java8Task::setContent, "處理一下這個任務(wù)")
                      .with(Java8Task::setStatus, 0)
                      .with(Java8Task::setFinishDate, new Date())
                      .build();
              System.out.println(java8Task);
          }

          這樣一來,任何帶有默認構(gòu)造器和 set 方法的類都可以使用這個通用的 Builder 模式了。

          雖然利用 Java 8 是實現(xiàn)了通用有 Builder 模式,但還是有很多冗余的代碼,而且本質(zhì)還是調(diào)用的 set 方法,所以和 set 比起來只是多了一個鏈式調(diào)用而已。

          Spring Boot 中的 Builder 模式

          Spring Boot 是現(xiàn)在主流的應(yīng)用框架,其中也用到了 Builder 模式,可見 Builder 模式的常見性。

          下面再來看下 Spring Boot 是怎么應(yīng)用 Builder 模式的:

          new SpringApplicationBuilder()
                  .sources(Parent.class)
                  .child(Application.class)
                  .bannerMode(Banner.Mode.OFF)
                  .run(args);

          如上代碼所示,這是 Spring Boot 的鏈式啟動方式。

          Spring Boot 基礎(chǔ)教程看這里:

          https://github.com/javastacks/spring-boot-best-practice

          我們來看它是怎么做的:

          它是新增了一個 XxxBuilder 類:SpringApplicationBuilder,然后在 SpringApplicationBuilder 中新增了個 SpringApplication 的成員變量,然后再新增變量對應(yīng)的方法。

          所以,Spring Boot 只是用 SpringApplicationBuilder 包裝了一下 SpringApplication 而已,寫法有所不同,但中心思想都是一樣的。這里就不再演示了,大家也可以了借鑒一下。

          總結(jié)

          本文說了同事寫的瘋狂的類構(gòu)造器,然后再介紹了用 set 方法改良,以及使用 4 種 Builder 模式改良的方式,下面來總結(jié)一下吧:

          • 常規(guī)的 Builder 模式
          • Lombok 實現(xiàn) Builder 模式(推薦)
          • Java 8 實現(xiàn) Builder 模式
          • Spring Boot 中的 Builder 模式

          如果團隊有使用 Lombok,那么 Lombok 無疑是最佳推薦的方式,代碼量少,使用簡單,方便維護。其他三種實現(xiàn)方式都各有利弊,大家都可以參考使用。

          總之,別再寫瘋狂的類構(gòu)造器了……

          如果你在公司看到同事寫這樣的優(yōu)秀代碼,請把這篇文章發(fā)給他。

          好了,今天的分享就到這里了,后面棧長我會更新更多  Java 技術(shù)實戰(zhàn)及設(shè)計模式系列文章,公眾號Java技術(shù)棧第一時間推送。

          本節(jié)教程所有實戰(zhàn)源碼已上傳到這個倉庫:

          https://github.com/javastacks/javastack

          最后,覺得我的文章對你用收獲的話,動動小手,給個在看、轉(zhuǎn)發(fā),原創(chuàng)不易,棧長需要你的鼓勵。

          版權(quán)申明:本文系公眾號 "Java技術(shù)棧" 原創(chuàng),原創(chuàng)實屬不易,轉(zhuǎn)載、引用本文內(nèi)容請注明出處,禁止抄襲、洗稿,請自重,尊重他人勞動成果和知識產(chǎn)權(quán)。






          關(guān)注Java技術(shù)棧看更多干貨



          獲取 Spring Boot 實戰(zhàn)筆記!
          瀏覽 27
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  啪啪啪免费网站视频 | 国产精品秘 入口swag | 美女三级视频 | 91麻豆精品国产91久久久ios版 | 欧美日皮的视频 |