這是設計模式系列文章的第三篇
之前兩篇的閱讀效果不是很好,我一度懷疑這種題材的文章不受大家歡迎,直到前兩天我面試了一個小姐姐...
面試過程中和小姐姐聊起她在上家公司做過的專案,其中有一個功能,根據小姐姐的描述,我第一感覺應該用生成器模式來實現
小姐姐說她並沒有用生成器模式,就是簡單的硬編碼
我問她為什麼不使用生成器模式實現的時候,小姐姐的一句話突破了我的認知下線
小姐姐說:我不知道什麼是生成器模式,我不打算做架構師,沒必要學設計模式
原來她認為設計模式只有在做架構設計的時候才會用到,跟普通程式設計師沒有關係
我覺得小姐姐的觀點存在嚴重問題,設計模式是程式設計師的基本技能,每個程式設計師都應該掌握並靈活應用
良好的程式碼設計不僅可以讓程式碼重複性更高,還能使程式碼更易讀從而降低程式碼後期的維護成本,最重要的是可以提高系統的可靠性
今天,我們就使用生成器模式來實現小姐姐的需求
實際案例
我們先來看一下這個小姐姐的專案的具體需求
根據使用者近期的消費金額、消費次數、瀏覽商品型別、商品價格區間等一些屬性,生成使用者畫像。根據畫像分析使用者行為,實現精準營銷或刺激消費等。
當然,不同的業務關注的角度也不同。比如精準營銷業務關注的是使用者近半年的資料,而且以消費資料為主;刺激消費業務關注的是使用者近一個月的資料,而且以常開啟的商品為主
從程式設計角度把需求提煉一下,大概就是以下兩點:
- 提供一個使用者物件,這個物件包括使用者名稱、消費金額、消費次數、瀏覽商品型別、商品價格區間等屬性
- 根據這個物件進行一些業務處理
我們先來看一下小姐姐當初是怎麼實現這個需求的
小姐姐的程式碼是在精準營銷和刺激消費的業務邏輯裡面,分別建立了一個User物件。
兩個業務中建立User物件的邏輯基本一樣,只有在獲取近期消費資料時稍有差別。一個是獲取近半年的資料,另一個是獲取近一個月的資料
這樣的硬編碼是把User物件的建立過程,嵌入到了其他業務邏輯裡面,這就造成一些問題
- 問題一:程式碼重用性降低
User物件的建立邏輯基本一樣,但是寫了兩遍。如果後期加入新的業務,User物件的建立邏輯還要再寫一遍,程式碼重用性太低
- 問題二:維護成本增大
示例中的虛擬碼模擬的比較簡單,實際上User物件的建立過程非常複雜,需要查詢各種資料並且對資料進行過濾、分類、整理,程式碼可能有幾百行
精準營銷或刺激消費的業務邏輯也是非常複雜的,把兩塊複雜的邏輯寫到一塊,後期閱讀或維護程式碼的成本將幾何倍的增長
- 問題三:程式碼耦合度增加
將兩塊業務邏輯寫到一起,其中不免會共享一些邏輯。
如果後期想對共享的邏輯進行修改,讓其僅對其中一方生效,程式碼的修改是很不友好的,很容易造成另一方的邏輯漏洞
我們可以嘗試使用生成器模式來解決這些問題
生成器模式
生成器模式定義
生成器模式是將一個複雜物件的構建與它的表示分離,使得同樣的構建過程可以建立不同的表示
換成大白話理解就是:一個複雜的物件,它的建立過程和使用過程要分開。對於物件的使用者來說,我只需要告訴建立者我需要使用這個複雜物件,至於這個複雜物件是怎麼建立的,不關我事 (ps:有點渣男的味道)
生成器模式使用場景
在建立一個物件時,同時滿足以下條件,可以使用生成器模式
- 物件的建立過程非常複雜
- 物件的建立步驟固定
- 不同的呼叫者獲得的物件不完全相同
如果需要建立的物件不復雜,這時候是沒必要使用生成器模式的。因為生成器模式本身的程式碼實現有一點複雜,使用它成本有點高,還不如簡單的硬編碼
如果物件的建立步驟不固定,也不推薦使用生成器模式。
假如在小姐姐的專案中,如果精準營銷需要使用者的消費資料,不需要瀏覽商品資料;刺激消費需要使用者的瀏覽商品資料,不需要消費資料。
User物件的建立步驟就是
兩個業務建立User物件的步驟是不一樣的,這時候不適合使用生成器模式
如果所有的呼叫者需要的物件完全一樣,也不需要使用生成器模式。
假如小姐姐的需求中,兩個業務關注的消費資料都是近一個月的,對消費資料和商品資料的關注度也是一樣的,就不需要使用生成器模式,只需要把User物件的建立過程進行單獨的封裝,兩個業務直接呼叫即可
生成器模式實戰
我們先來看一下生成器模式的架構
套用到我們需求中,User
物件的建立就是下面這個樣子
下面使用程式碼實現小姐姐的需求,首先定義我們要建立的物件,也就是 User
類
public class User {
private String nickname;
private int payCnt;
private int payAmt;
private List<String> productType;
private List<String> amtInterval;
public void setNickname(String nickname) {
this.nickname = nickname;
}
public void setPayCnt(int payCnt) {
this.payCnt = payCnt;
}
public void setPayAmt(int payAmt) {
this.payAmt = payAmt;
}
public void setProductType(List<String> productType) {
this.productType = productType;
}
public void setAmtInterval(List<String> amtInterval) {
this.amtInterval = amtInterval;
}
}
第二步,編寫構造介面定義建立 User
物件需要的步驟,並提供返回 User
物件的方法
public interface IUserBuilder {
// 構建使用者暱稱
String buildNicaname();
// 構建使用者消費次數,days代表最近天數
int buildPayCnt();
// 構建使用者消費金額,days代表最近天數
int buildPayAmt();
// 構建使用者經常瀏覽商品型別
List<String> buildProductType();
// 構建使用者經常瀏覽商品價格區間
List<String> buildAmtInterval();
// 獲取user物件
User getUser();
}
第三步,編寫構造介面的具體實現類,重寫每一個方法,編寫每一個方法的具體實現邏輯。
public class UserBuilder implements IUserBuilder {
private String days;
public UserBuilder(String days) {
this.days = days;
}
@Override
public String buildNicaname() {
String nicaname = "赫連小伍";
System.out.println("查詢使用者暱稱為:" + nicaname);
return nicaname;
}
@Override
public int buildPayCnt() {
int payCnt = 0;
if ("30".equals(days)) {
payCnt = 1;
} else{
payCnt = 10;
}
System.out.println("查詢使用者近" + days + "天的消費筆數為:" + payCnt);
return payCnt;
}
@Override
public int buildPayAmt() {
int payAmt = 0;
if ("30".equals(days)) {
payAmt = 2;
} else{
payAmt = 100;
}
System.out.println("查詢使用者近" + days + "天的消費金額為:" + payAmt);
return payAmt;
}
@Override
public List<String> buildProductType() {
List<String> list = new ArrayList<>();
list.add("增發劑");
list.add("格子衫");
System.out.println("查詢使用者瀏覽的商品型別為:" + list);
return list;
}
@Override
public List<String> buildAmtInterval() {
List<String> list = new ArrayList<>();
list.add("1-9");
list.add("2-10");
System.out.println("查詢使用者瀏覽的商品價格區間為:" + list);
return list;
}
@Override
public User getUser() {
User user = new User();
user.setNickname(this.buildNicaname());
user.setPayCnt(this.buildPayCnt());
user.setPayAmt(this.buildPayAmt());
user.setProductType(this.buildProductType());
user.setAmtInterval(this.buildAmtInterval());
return user;
}
}
第四步,編寫 Director
類,對精準營銷和刺激消費兩塊業務分別提供對應的獲取 User
的方法。這裡為了方便呼叫,方法全部採用 static
的
public class Director {
// 為精準營銷提供獲取User的方法
public static User getJzyxUser() {
IUserBuilder userBuilder = new UserBuilder("360");
return userBuilder.getUser();
}
// 為刺激消費提供獲取User的方法
public static User getCjxfUser() {
IUserBuilder userBuilder = new UserBuilder("30");
return userBuilder.getUser();
}
}
最後一步,模擬精準營銷和刺激消費的業務,分別獲取對應的 User
物件
public static void main(String[] args) {
// 模擬精準營銷業務邏輯
User jzyxUser = Director.getJzyxUser();
System.out.println("精準營銷獲得的User物件為:" + jzyxUser);
System.out.println("開始精準營銷的業務邏輯");
// 模擬刺激消費業務邏輯
User cjxfUser = Director.getCjxfUser();
System.out.println("刺激消費獲得的User物件為:" + cjxfUser);
System.out.println("開始刺激消費的業務邏輯");
}
這就用生成器模式實現了小姐姐的需求
對於精準營銷或刺激消費的業務邏輯來說,它們不用再關心 User
物件的建立過程,可以更專注於自身的業務邏輯,無論是程式碼閱讀或後期維護都更方便
總結
生成器模式也被稱作建立者模式或建造者模式,它屬於設計模式三大型別中的建立型模式
與工廠模式相比,生成器模式更善於處理建立步驟固定的複雜物件。它與工廠模式並沒有很明顯的界限,在許多設計初期,大部分程式設計師都習慣用工廠方法模式來構建程式碼,隨著業務變得複雜,程式碼也會不斷的重構。程式碼架構也逐漸的演變成抽象工廠模式、生成器模式
生成器模式也不能頻繁的使用,如果專案的內部變化複雜,可能會導致需要定義很多具體生成器類來實現這種變化,導致系統變得很龐大
每一種設計模式都有利有弊,權衡利弊後找出適合自己專案的模式才會使程式碼變得更 “完美”
-- 以上內容來自公眾號 赫連小伍,轉載請註明出處