設計模式(三):生成器模式
這是設計模式系列文章的第三篇
之前兩篇的閱讀效果不是很好,我一度懷疑這種題材的文章不受大家歡迎,直到前兩天我面試了一個小姐姐…
面試過程中和小姐姐聊起她在上家公司做過的項目,其中有一個功能,根據小姐姐的描述,我第一感覺應該用生成器模式來實現
小姐姐說她並沒有用生成器模式,就是簡單的硬編碼
我問她為什麼不使用生成器模式實現的時候,小姐姐的一句話突破了我的認知下線
小姐姐說:我不知道什麼是生成器模式,我不打算做架構師,沒必要學設計模式
原來她認為設計模式只有在做架構設計的時候才會用到,跟普通程式設計師沒有關係
我覺得小姐姐的觀點存在嚴重問題,設計模式是程式設計師的基本技能,每個程式設計師都應該掌握並靈活應用
良好的程式碼設計不僅可以讓程式碼重複性更高,還能使程式碼更易讀從而降低程式碼後期的維護成本,最重要的是可以提高系統的可靠性
今天,我們就使用生成器模式來實現小姐姐的需求
實際案例
我們先來看一下這個小姐姐的項目的具體需求
根據用戶近期的消費金額、消費次數、瀏覽商品類型、商品價格區間等一些屬性,生成用戶畫像。根據畫像分析用戶行為,實現精準營銷或刺激消費等。
當然,不同的業務關注的角度也不同。比如精準營銷業務關注的是用戶近半年的數據,而且以消費數據為主;刺激消費業務關注的是用戶近一個月的數據,而且以常打開的商品為主
從編程角度把需求提煉一下,大概就是以下兩點:
- 提供一個用戶對象,這個對象包括用戶名、消費金額、消費次數、瀏覽商品類型、商品價格區間等屬性
- 根據這個對象進行一些業務處理
我們先來看一下小姐姐當初是怎麼實現這個需求的
小姐姐的程式碼是在精準營銷和刺激消費的業務邏輯裡面,分別創建了一個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
對象的創建過程,可以更專註於自身的業務邏輯,無論是程式碼閱讀或後期維護都更方便
總結
生成器模式也被稱作創建者模式或建造者模式,它屬於設計模式三大類型中的創建型模式
與工廠模式相比,生成器模式更善於處理創建步驟固定的複雜對象。它與工廠模式並沒有很明顯的界限,在許多設計初期,大部分程式設計師都習慣用工廠方法模式來構建程式碼,隨著業務變得複雜,程式碼也會不斷的重構。程式碼架構也逐漸的演變成抽象工廠模式、生成器模式
生成器模式也不能頻繁的使用,如果項目的內部變化複雜,可能會導致需要定義很多具體生成器類來實現這種變化,導致系統變得很龐大
每一種設計模式都有利有弊,權衡利弊後找出適合自己項目的模式才會使程式碼變得更 「完美」
— 以上內容來自公眾號 赫連小伍,轉載請註明出處