面向對象編程內功心法系列九(聊一聊建造者模式)

1.引子

關於建立型設計模式,咱們已經分享了單例設計模式,和工廠設計模式。建立型設計模式其實不難理解,咱們抓住本質,所謂建立型設計模式都與對象的建立相關,而且在代碼實現層面區分度都比較高,你應該都還記得如何實現單例設計模式(懶漢式、餓漢式),如何實現工廠模式(簡單工廠、工廠方法、抽象工廠)對吧java

那麼今天,我將繼續給你分享另一個在實際開發中,使用比較多的建立型設計模式,它就是建造者設計模式。而且我將重點給你分享爲何須要建造者設計模式?以及建造者設計模式解決了什麼問題?我想這個對於咱們掌握設計模式會更加劇要一些,知道作什麼爲何。至於代碼層面,有時候還次要一些。程序員

讓咱們開始吧。設計模式

2.案例

2.1.給一個須要建造者設計模式的理由

暫時咱們拋開設計模式,迴歸本質。請你輕輕的閉上眼睛,慢慢地深呼吸,是否是感受到一陣早春的氣息撲面而來!測試

停!請你不要瞎想了。咱們是程序員,不是詩人。你應該聽見的是鍵盤聲、電流聲聲聲入耳,綿綿不絕,而不是流水叮咚聲!ui

回來吧,想想對象是如何建立的?this

平常開發中,當咱們須要一個對象的時候,咱們一般會經過構造方法建立對象,或者再進一步經過set方法初始化成員變量,像下面這樣spa

用戶實體類設計

public class User{
    private String name;
    private Integer sex;
    private Integer age;
    
    public User(){}
    
    public User(String name, Integer sex, Integer age){
        this.name = name;
        this.sex = sex;
        this.age = age;
    }
    
    /***************省略get方法****************/
    public void setName(String name){
        this.name = name;
    }
    
    public void setSex(Integer sex){
        this.sex = sex;
    }
    
    public setAge(Integer age){
        this.age = age;
    }
}

建立用戶對象code

// 方式一:直接經過構造方法
User user = new User("小明",1,18);

// 方式二:經過無參構造方法 + set方法
User user = new User();
user.setName("小明");
user.setSex(1);
user.setAge(18);

以上建立用戶User對象的代碼,咱們很是熟悉。那麼你是否曾想過,這樣建立使用對象的方式有什麼問題嗎?你能夠先簡單的思考一下,再往下看我提出的問題。對象

  • 思考一:若是咱們要建立的對象,成員變量很是多。好比說有20個、30個、甚至更多。你會怎麼作?經過帶參數構造方法,構造方法會由於參數太多,從而致使代碼易用性、可讀性差。試想調用一個帶30個參數的構造方法,心理陰影面積有多大,像這樣

User user = new User(參數1,參數2,參數3,......參數30);
  • 思考二:你說User類的成員變量雖然多,可是咱們在實際使用的時候,並非全部成員變量都要賦值。這樣一來就好辦了,經過無參數構造方法+set方法,像這樣
User user = new User();
user.set參數1(參數1);
user.set參數2(參數2);
......給須要的成員變量,經過set方法賦值......
......用不到的成員變量,就不用管了,問題解決......
  • 思考三:經過無參數構造方法+set方法,從某種角度上解決了咱們的問題。可是,若是成員變量須要有校驗邏輯(好比非空,好比取值範圍等);再好比成員變量之間有依賴關係校驗邏輯(性別男,退休年齡60歲;性別女,退休年齡55歲)。這樣的校驗邏輯,該放到什麼地方處理呢?構造方法中?set方法中?彷佛都不合適!咱們須要一個統一的地方,來統一處理校驗邏輯。這個時候咱們本文的主角建造者設計模式,終於舉手站了起來講:是了,這些事情我來處理最合適了

經過平常開發中,咱們都很是熟悉的一個案例,以及思考1、思考2、思考三幾個問題。咱們引出了建造者設計模式的適用場景,來簡單總結一下

  • 若是要建立的目標對象,成員變量比較多,且對象的使用比較靈活(每次建立對象,不須要所有的成員變量,而是部分紅員變量的組合)

  • 若是要建立的目標對象,成員變量須要進行必定的業務校驗,甚至成員變量之間有業務依賴關係校驗

基於以上兩點,咱們適合使用建造者設計模式,也是我想分享給你:須要建造者設計模式的理由。接下來咱們經過代碼示例來演示建造者設計模式的實現。

2.2.代碼示例

2.2.1.用戶實體User類

/**
 * 用戶實體類
 *
 * @author ThinkPad
 * @version 1.0
 * @date 2021/3/5 20:11
 */
public class User {

    /**
     * 姓名
     * */
    private String name;
    /**
     * 性別:1 男  ; 2 女
     * */
    private Integer sex;
    /**
     * 退休年齡:男 60 歲;女 55歲
     * */
    private Integer retirementAge;

    /*****************get方法***************************/
    public String getName() {
        return name;
    }

    public Integer getSex() {
        return sex;
    }

    public Integer getRetirementAge() {
        return retirementAge;
    }

    /*****************set方法***************************/
    public void setName(String name) {
        this.name = name;
    }

    public void setSex(Integer sex) {
        this.sex = sex;
    }

    public void setRetirementAge(Integer retirementAge) {
        this.retirementAge = retirementAge;
    }

    /**
     * 用戶建造者設計類
     */
    public static class UserBuilder{
        /**拷貝用戶實體類成員變量*/
        private String name;
        private Integer sex;
        private Integer retirementAge;

        /**無參數構造方法*/
        UserBuilder(){}

        /**至關於set方法,注意返回builder對象*/
       public User.UserBuilder name(String name){
            this.name = name;
            return this;
       }

        public User.UserBuilder sex(Integer sex){
            this.sex = sex;
            return this;
        }

        public User.UserBuilder retirementAge(Integer retirementAge){
            this.retirementAge = retirementAge;
            return this;
        }

        /**
         * 建造者設計模式代碼實現,核心方法
         * 此方法中,校驗姓名不能爲空
         * 此方法中,模擬校驗性別與退休年齡的依賴關係,即:
         * 1.性別男,退休年齡60歲
         * 2.性別女,退休年齡55歲
         * */
        public User build(){
            // 校驗姓名不能爲空
           if(this.name == null || "".equals(this.name)){
               throw new RuntimeException("用戶姓名不能爲空!");
           }

           // 校驗性別與退休年齡依賴關係
            if(this.sex == 1 && this.retirementAge < 60){
               throw new RuntimeException("男人命苦,退休年齡必須是60歲之後!");
            }else if(this.sex == 2 && this.retirementAge < 55){
                throw new RuntimeException("女人也不容易,退休年齡必須是55歲之後!");
            }

            // 建立用戶對象
            User user = new User();
            user.setName(this.name);
            user.setSex(this.sex);
            user.setRetirementAge(this.retirementAge);

           return user;
        }

    }

}

2.2.2.用戶建造者UserBuilder類

建造者設計模式的應用,不少時候咱們會直接把建造者類,在它所對應的目標類中實現,爲的是加強代碼的內聚性。好比UserBuilder,在User類的內部。

這裏我單獨把UserBuilder類的代碼貼出來,是方便你更清晰的看到建造者設計模式實現的關鍵地方,主要有三個

  • 建造者類的成員變量,與目標類的成員變量一致

  • 建造者類,須要給每一個成員變量提供對應的,至關於set賦值方法

  • 建造者類,重點是提供build方法,該方法實現業務邏輯的統一處理,以及構建目標類對象並返回

/**
* 用戶建造者設計類
*/
public static class UserBuilder{
 /**拷貝用戶實體類成員變量*/
 private String name;
 private Integer sex;
 private Integer retirementAge;

 /**無參數構造方法*/
 UserBuilder(){}

 /**至關於set方法,注意返回builder對象*/
 public User.UserBuilder name(String name){
     this.name = name;
     return this;
 }

 public User.UserBuilder sex(Integer sex){
   this.sex = sex;
   return this;
 }

 public User.UserBuilder retirementAge(Integer retirementAge){
   this.retirementAge = retirementAge;
   return this;
 }

 /**
 * 建造者設計模式代碼實現,核心方法
 * 此方法中,校驗姓名不能爲空
 * 此方法中,模擬校驗性別與退休年齡的依賴關係,即:
 * 1.性別男,退休年齡60歲
 * 2.性別女,退休年齡55歲
 * */
 public User build(){
  // 校驗姓名不能爲空
  if(this.name == null || "".equals(this.name)){
    throw new RuntimeException("用戶姓名不能爲空!");
   }

   // 校驗性別與退休年齡依賴關係
   if(this.sex == 1 && this.retirementAge < 60){
      throw new RuntimeException("男人命苦,退休年齡必須是60歲之後!");
   }else if(this.sex == 2 && this.retirementAge < 55){
     throw new RuntimeException("女人也不容易,退休年齡必須是55歲之後!");
   }

   // 建立用戶對象
   User user = new User();
   user.setName(this.name);
   user.setSex(this.sex);
   user.setRetirementAge(this.retirementAge);

   return user;
 }

}

2.2.3.使用建造者設計模式

2.2.3.1.正常執行案例

 

public static void main(String[] args) {
    // 建立建造者對象
   User.UserBuilder builder = new User.UserBuilder();
   // 經過建造者,建立用戶對象
   User user = builder.name("小明")
           .sex(1)
           .retirementAge(60)
           .build();

   // 輸出用戶對象
  System.out.println("用戶名稱:" + user.getName()
        + ",性別:" + user.getSex()
        + ",退休年齡:" + user.getRetirementAge());
}

#執行結果==================================
用戶名稱:小明,性別:1,退休年齡:60

Process finished with exit code 0

2.2.3.2.測試校驗異常案例

public static void main(String[] args) {
    // 建立建造者對象
   User.UserBuilder builder = new User.UserBuilder();
   // 經過建造者,建立用戶對象
   User user = builder.name("小明")
           .sex(1)
           .retirementAge(59)// 設置退休年齡 59歲,不知足男人  60歲退休的約束
           .build();

   // 輸出用戶對象
  System.out.println("用戶名稱:" + user.getName()
        + ",性別:" + user.getSex()
        + ",退休年齡:" + user.getRetirementAge());
}

#執行結果==================================
Exception in thread "main" java.lang.RuntimeException: 男人命苦,退休年齡必須是60歲之後!
	at com.anan.edu.common.design.pattern.create.builder.User$UserBuilder.build(User.java:94)
	at com.anan.edu.common.design.pattern.create.builder.BuilderDemo.main(BuilderDemo.java:19)

Process finished with exit code 1
相關文章
相關標籤/搜索