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