作者:小傅哥 博客:https:// - 原創(chuàng)系列專題文章
沉淀、分享、成長,讓自己和他人都能有所收獲!😄
一、前言
寫好代碼三個(gè)關(guān)鍵點(diǎn)
如果把寫代碼想象成家里的軟裝,你肯定會想到家里需要有一個(gè)非常不錯(cuò)格局最好是南北通透的,買回來的家具最好是品牌保證質(zhì)量的,之后呢是大小合適,不能擺放完了看著別扭。那么把這一過程抽象成寫代碼就是需要三個(gè)核心的關(guān)鍵點(diǎn);架構(gòu)
(房間的格局)、命名
(品牌和質(zhì)量)、注釋
(尺寸大小說明書),只有這三個(gè)點(diǎn)都做好才能完成出一套賞心悅目的家 。
平原走碼🐎易放難收
上學(xué)期間你寫了多少代碼?上班一年你能寫多少代碼?回家自己學(xué)習(xí)寫了多少代碼?個(gè)人素養(yǎng)的技術(shù)棧地基都是一塊一塊磚碼出來的,寫的越廣越深,根基就越牢固。當(dāng)根基牢固了以后在再上層建設(shè)就變得迎刃而解了,也更容易建設(shè)了。往往最難的就是一層一層階段的突破,突破就像破殼一樣,也像夯實(shí)地基,短時(shí)間看不到成績,也看不出高度。但以后誰能走的穩(wěn),就靠著默默的沉淀。
技術(shù)傳承的重要性
可能是現(xiàn)在時(shí)間節(jié)奏太快,一個(gè)需求下來恨不得當(dāng)天就上線(這個(gè)需求很簡單,怎么實(shí)現(xiàn)我不管,明天上線!
),導(dǎo)致團(tuán)隊(duì)的人都很慌
、很急
、很累
、很崩潰
,最終反反復(fù)復(fù)的人員更替,項(xiàng)目在這個(gè)過程中也交接了N次,文檔不全、代碼混亂、錯(cuò)綜復(fù)雜,誰在后面接手也都只能修修補(bǔ)補(bǔ),就像爛尾樓。這個(gè)沒有傳承、沒有沉淀的項(xiàng)目,很難跟隨業(yè)務(wù)的發(fā)展。最終!根基不牢,一地雞毛。
二、開發(fā)環(huán)境
JDK 1.8 Idea + Maven 涉及工程三個(gè),可以通過關(guān)注公眾號 :bugstack蟲洞棧
,回復(fù)源碼下載
獲取(打開獲取的鏈接,找到序號18)
工程 描述 itstack-demo-design-19-00 場景模擬工程;模擬營銷活動操作服務(wù)(查詢、審核) itstack-demo-design-19-01 使用一坨代碼實(shí)現(xiàn)業(yè)務(wù)需求 itstack-demo-design-19-02 通過設(shè)計(jì)模式優(yōu)化改造代碼,產(chǎn)生對比性從而學(xué)習(xí)
三、狀態(tài)模式介紹
狀態(tài)模式描述的是一個(gè)行為下的多種狀態(tài)變更,比如我們最常見的一個(gè)網(wǎng)站的頁面,在你登錄與不登錄下展示的內(nèi)容是略有差異的(不登錄不能展示個(gè)人信息
),而這種登錄
與不登錄
就是我們通過改變狀態(tài) ,而讓整個(gè)行為發(fā)生了變化。
至少80后、90后的小伙伴基本都用過這種磁帶放音機(jī)(可能沒有這個(gè)好看
),它的上面是一排按鈕,當(dāng)放入磁帶后,通過上面的按鈕就可以讓放音機(jī)播放磁帶上的內(nèi)容(listen to 英語聽力考試
),而且有些按鈕是互斥的,當(dāng)在某個(gè)狀態(tài)下才可以按另外的按鈕(這在設(shè)計(jì)模式里也是一個(gè)關(guān)鍵的點(diǎn)
)。
四、案例場景模擬
在本案例中我們模擬營銷活動審核狀態(tài)流轉(zhuǎn)場景(一個(gè)活動的上線是多個(gè)層級審核上線的)
在上圖中也可以看到我們的流程節(jié)點(diǎn)中包括了各個(gè)狀態(tài)到下一個(gè)狀態(tài)扭轉(zhuǎn)的關(guān)聯(lián)條件,比如;審核通過才能到活動中,而不能從編輯中直接到活動中,而這些狀態(tài)的轉(zhuǎn)變就是我們要完成的場景處理。
大部分程序員基本都開發(fā)過類似的業(yè)務(wù)場景,需要對活動或者一些配置需要審核后才能對外發(fā)布,而這個(gè)審核的過程往往會隨著系統(tǒng)的重要程度而設(shè)立多級控制,來保證一個(gè)活動可以安全上線,避免造成資損。
當(dāng)然有時(shí)候會用到一些審批流的過程配置,也是非常方便開發(fā)類似的流程的,也可以在配置中設(shè)定某個(gè)節(jié)點(diǎn)的審批人員。但這不是我們主要體現(xiàn)的點(diǎn),在本案例中我們主要是模擬學(xué)習(xí)對一個(gè)活動的多個(gè)狀態(tài)節(jié)點(diǎn)的審核控制。
1. 場景模擬工程
itstack- demo- design- 19 - 00
└── src
└── main
└── java
└── org. itstack. demo. design
├── ActivityInfo. java
├── Status. java
└── ActivityService. java
在這個(gè)模擬工程里我們提供了三個(gè)類,包括;狀態(tài)枚舉(Status
)、活動對象(ActivityInfo
)、活動服務(wù)(ActivityService
),三個(gè)服務(wù)類。 接下來我們就分別介紹三個(gè)類包括的內(nèi)容。
2. 代碼實(shí)現(xiàn)
2.1 基本活動信息
public class ActivityInfo {
private String activityId; // 活動ID
private String activityName; // 活動名稱
private Enum< Status> status; // 活動狀態(tài)
private Date beginTime; // 開始時(shí)間
private Date endTime; // 結(jié)束時(shí)間
// ...get/set
}
一些基本的活動信息;活動ID、活動名稱、活動狀態(tài)、開始時(shí)間、結(jié)束時(shí)間。
2.2 活動枚舉狀態(tài)
public enum Status {
// 1創(chuàng)建編輯、2待審核、3審核通過(任務(wù)掃描成活動中)、4審核拒絕(可以撤審到編輯狀態(tài))、5活動中、6活動關(guān)閉、7活動開啟(任務(wù)掃描成活動中)
Editing, Check, Pass, Refuse, Doing, Close, Open
}
活動的枚舉;1創(chuàng)建編輯、2待審核、3審核通過(任務(wù)掃描成活動中)、4審核拒絕(可以撤審到編輯狀態(tài))、5活動中、6活動關(guān)閉、7活動開啟(任務(wù)掃描成活動中)
2.3 活動服務(wù)接口
public class ActivityService {
private static Map< String, Enum< Status> > statusMap = new ConcurrentHashMap < String, Enum< Status> > ( ) ;
public static void init ( String activityId, Enum< Status> status) {
// 模擬查詢活動信息
ActivityInfo activityInfo = new ActivityInfo ( ) ;
activityInfo. setActivityId ( activityId) ;
activityInfo. setActivityName ( "早起學(xué)習(xí)打卡領(lǐng)獎(jiǎng)活動" ) ;
activityInfo. setStatus ( status) ;
activityInfo. setBeginTime ( new Date ( ) ) ;
activityInfo. setEndTime ( new Date ( ) ) ;
statusMap. put ( activityId, status) ;
}
/**
* 查詢活動信息
*
* @param activityId 活動ID
* @return 查詢結(jié)果
*/
public static ActivityInfo queryActivityInfo ( String activityId) {
// 模擬查詢活動信息
ActivityInfo activityInfo = new ActivityInfo ( ) ;
activityInfo. setActivityId ( activityId) ;
activityInfo. setActivityName ( "早起學(xué)習(xí)打卡領(lǐng)獎(jiǎng)活動" ) ;
activityInfo. setStatus ( statusMap. get ( activityId) ) ;
activityInfo. setBeginTime ( new Date ( ) ) ;
activityInfo. setEndTime ( new Date ( ) ) ;
return activityInfo;
}
/**
* 查詢活動狀態(tài)
*
* @param activityId 活動ID
* @return 查詢結(jié)果
*/
public static Enum< Status> queryActivityStatus ( String activityId) {
return statusMap. get ( activityId) ;
}
/**
* 執(zhí)行狀態(tài)變更
*
* @param activityId 活動ID
* @param beforeStatus 變更前狀態(tài)
* @param afterStatus 變更后狀態(tài) b
*/
public static synchronized void execStatus ( String activityId, Enum< Status> beforeStatus, Enum< Status> afterStatus) {
if ( ! beforeStatus. equals ( statusMap. get ( activityId) ) ) return ;
statusMap. put ( activityId, afterStatus) ;
}
}
在這個(gè)靜態(tài)類中提供了活動的查詢和狀態(tài)變更接口;queryActivityInfo
、queryActivityStatus
、execStatus
。 同時(shí)使用Map的結(jié)構(gòu)來記錄活動ID和狀態(tài)變化信息,另外還有init方法來初始化活動數(shù)據(jù)。實(shí)際的開發(fā)中這類信息基本都是從數(shù)據(jù)庫
或者Redis
中獲取。
五、用一坨坨代碼實(shí)現(xiàn)
這里我們先使用最粗暴的方式來實(shí)現(xiàn)功能
對于這樣各種狀態(tài)的變更,最讓我們直接想到的就是使用if
和else
進(jìn)行判斷處理。每一個(gè)狀態(tài)可以流轉(zhuǎn)到下一個(gè)什么狀態(tài),都可以使用嵌套的if
實(shí)現(xiàn)。
1. 工程結(jié)構(gòu)
itstack- demo- design- 19 - 01
└── src
└── main
└── java
└── org. itstack. demo. design
├── ActivityExecStatusController. java
└── Result. java
整個(gè)實(shí)現(xiàn)的工程結(jié)構(gòu)比較簡單,只包括了兩個(gè)類;ActivityExecStatusController
、Result
,一個(gè)是處理流程狀態(tài),另外一個(gè)是返回的對象。
2. 代碼實(shí)現(xiàn)
public class ActivityExecStatusController {
/**
* 活動狀態(tài)變更
* 1. 編輯中 -> 提審、關(guān)閉
* 2. 審核通過 -> 拒絕、關(guān)閉、活動中
* 3. 審核拒絕 -> 撤審、關(guān)閉
* 4. 活動中 -> 關(guān)閉
* 5. 活動關(guān)閉 -> 開啟
* 6. 活動開啟 -> 關(guān)閉
*
* @param activityId 活動ID
* @param beforeStatus 變更前狀態(tài)
* @param afterStatus 變更后狀態(tài)
* @return 返回結(jié)果
*/
public Result execStatus ( String activityId, Enum< Status> beforeStatus, Enum< Status> afterStatus) {
// 1. 編輯中 -> 提審、關(guān)閉
if ( Status. Editing. equals ( beforeStatus) ) {
if ( Status. Check. equals ( afterStatus) || Status. Close. equals ( afterStatus) ) {
ActivityService. execStatus ( activityId, beforeStatus, afterStatus) ;
return new Result ( "0000" , "變更狀態(tài)成功" ) ;
} else {
return new Result ( "0001" , "變更狀態(tài)拒絕" ) ;
}
}
// 2. 審核通過 -> 拒絕、關(guān)閉、活動中
if ( Status. Pass. equals ( beforeStatus) ) {
if ( Status. Refuse. equals ( afterStatus) || Status. Doing. equals ( afterStatus) || Status. Close. equals ( afterStatus) ) {
ActivityService. execStatus ( activityId, beforeStatus, afterStatus) ;
return new Result ( "0000" , "變更狀態(tài)成功" ) ;
} else {
return new Result ( "0001" , "變更狀態(tài)拒絕" ) ;
}
}
// 3. 審核拒絕 -> 撤審、關(guān)閉
if ( Status. Refuse. equals ( beforeStatus) ) {
if ( Status. Editing. equals ( afterStatus) || Status. Close. equals ( afterStatus) ) {
ActivityService. execStatus ( activityId, beforeStatus, afterStatus) ;
return new Result ( "0000" , "變更狀態(tài)成功" ) ;
} else {
return new Result ( "0001" , "變更狀態(tài)拒絕" ) ;
}
}
// 4. 活動中 -> 關(guān)閉
if ( Status. Doing. equals ( beforeStatus) ) {
if ( Status. Close. equals ( afterStatus) ) {
ActivityService. execStatus ( activityId, beforeStatus, afterStatus) ;
return new Result ( "0000" , "變更狀態(tài)成功" ) ;
} else {
return new Result ( "0001" , "變更狀態(tài)拒絕" ) ;
}
}
// 5. 活動關(guān)閉 -> 開啟
if ( Status. Close. equals ( beforeStatus) ) {
if ( Status. Open. equals ( afterStatus) ) {
ActivityService. execStatus ( activityId, beforeStatus, afterStatus) ;
return new Result ( "0000" , "變更狀態(tài)成功" ) ;
} else {
return new Result ( "0001" , "變更狀態(tài)拒絕" ) ;
}
}
// 6. 活動開啟 -> 關(guān)閉
if ( Status. Open. equals ( beforeStatus) ) {
if ( Status. Close. equals ( afterStatus) ) {
ActivityService. execStatus ( activityId, beforeStatus, afterStatus) ;
return new Result ( "0000" , "變更狀態(tài)成功" ) ;
} else {
return new Result ( "0001" , "變更狀態(tài)拒絕" ) ;
}
}
return new Result ( "0001" , "非可處理的活動狀態(tài)變更" ) ;
}
}
這里我們只需要看一下代碼實(shí)現(xiàn)的結(jié)構(gòu)即可。從上到下是一整篇的ifelse
,基本這也是大部分初級程序員的開發(fā)方式。 這樣的面向過程式開發(fā)方式,對于不需要改動代碼,也不需要二次迭代的,還是可以使用的(但基本不可能不迭代
)。而且隨著狀態(tài)和需求變化,會越來越難以維護(hù),后面的人也不好看懂并且很容易填充其他的流程進(jìn)去。越來越亂就是從點(diǎn)滴開始的
3. 測試驗(yàn)證
3.1 編寫測試類
@Test
public void test ( ) {
// 初始化數(shù)據(jù)
String activityId = "100001" ;
ActivityService. init ( activityId, Status. Editing) ;
ActivityExecStatusController activityExecStatusController = new ActivityExecStatusController ( ) ;
Result resultRefuse = activityExecStatusController. execStatus ( activityId, Status. Editing, Status. Refuse) ;
logger. info ( "測試結(jié)果(編輯中To審核拒絕):{}" , JSON. toJSONString ( resultRefuse) ) ;
Result resultCheck = activityExecStatusController. execStatus ( activityId, Status. Editing, Status. Check) ;
logger. info ( "測試結(jié)果(編輯中To提交審核):{}" , JSON. toJSONString ( resultCheck) ) ;
}
我們的測試代碼包括了兩個(gè)功能的驗(yàn)證,一個(gè)是從編輯中
到審核拒絕
,另外一個(gè)是從編輯中到提交審核
。 因?yàn)閺奈覀兊膱鼍傲鞒讨锌梢钥吹?#xff0c;編輯中的活動是不能直接到審核拒絕
的,這中間還需要提審
。
3.2 測試結(jié)果
23 : 24 : 30.774 [ main] INFO org. itstack. demo. design. test. ApiTest - 測試結(jié)果( 編輯中To審核拒絕) :{ "code" : "0001" , "info" : "變更狀態(tài)拒絕" }
23 : 24 : 30.778 [ main] INFO org. itstack. demo. design. test. ApiTest - 測試結(jié)果( 編輯中To提交審核) :{ "code" : "0000" , "info" : "變更狀態(tài)成功" }
Process finished with exit code 0
從測試結(jié)果和我們的狀態(tài)流程的流轉(zhuǎn)中可以看到,是符合測試結(jié)果預(yù)期的。除了不好維護(hù)外,這樣的開發(fā)過程還是蠻快的,但不建議這么搞!
六、狀態(tài)模式重構(gòu)代碼
接下來使用狀態(tài)模式來進(jìn)行代碼優(yōu)化,也算是一次很小的重構(gòu)。
重構(gòu)的重點(diǎn)往往是處理掉ifelse
,而想處理掉ifelse
基本離不開接口 與抽象類 ,另外還需要重新改造代碼結(jié)構(gòu)。
1. 工程結(jié)構(gòu)
itstack- demo- design- 19 - 02
└── src
└── main
└── java
└── org. itstack. demo. design
├── event
│ ├── CheckState. java
│ └── CloseState. java
│ └── DoingState. java
│ └── EditingState. java
│ └── OpenState. java
│ └── PassState. java
│ └── RefuseState. java
├── Result. java
├── State. java
└── StateHandler. java
狀態(tài)模式模型結(jié)構(gòu)
以上是狀態(tài)模式的整個(gè)工程結(jié)構(gòu)模型,State是一個(gè)抽象類,定義了各種操作接口(提審、審核、拒審等
)。 右側(cè)的不同顏色狀態(tài)與我們場景模擬中的顏色保持一致,是各種狀態(tài)流程流轉(zhuǎn)的實(shí)現(xiàn)操作。這里的實(shí)現(xiàn)有一個(gè)關(guān)鍵點(diǎn)就是每一種狀態(tài)到下一個(gè)狀態(tài),都分配到各個(gè)實(shí)現(xiàn)方法中控制,也就不需要if
語言進(jìn)行判斷了。 最后是StateHandler
對狀態(tài)流程的統(tǒng)一處理,里面提供Map
結(jié)構(gòu)的各項(xiàng)服務(wù)接口調(diào)用,也就避免了使用if
判斷各項(xiàng)狀態(tài)轉(zhuǎn)變的流程。
2. 代碼實(shí)現(xiàn)
2.1 定義狀態(tài)抽象類
public abstract class State {
/**
* 活動提審
*
* @param activityId 活動ID
* @param currentStatus 當(dāng)前狀態(tài)
* @return 執(zhí)行結(jié)果
*/
public abstract Result arraignment ( String activityId, Enum< Status> currentStatus) ;
/**
* 審核通過
*
* @param activityId 活動ID
* @param currentStatus 當(dāng)前狀態(tài)
* @return 執(zhí)行結(jié)果
*/
public abstract Result checkPass ( String activityId, Enum< Status> currentStatus) ;
/**
* 審核拒絕
*
* @param activityId 活動ID
* @param currentStatus 當(dāng)前狀態(tài)
* @return 執(zhí)行結(jié)果
*/
public abstract Result checkRefuse ( String activityId, Enum< Status> currentStatus) ;
/**
* 撤審撤銷
*
* @param activityId 活動ID
* @param currentStatus 當(dāng)前狀態(tài)
* @return 執(zhí)行結(jié)果
*/
public abstract Result checkRevoke ( String activityId, Enum< Status> currentStatus) ;
/**
* 活動關(guān)閉
*
* @param activityId 活動ID
* @param currentStatus 當(dāng)前狀態(tài)
* @return 執(zhí)行結(jié)果
*/
public abstract Result close ( String activityId, Enum< Status> currentStatus) ;
/**
* 活動開啟
*
* @param activityId 活動ID
* @param currentStatus 當(dāng)前狀態(tài)
* @return 執(zhí)行結(jié)果
*/
public abstract Result open ( String activityId, Enum< Status> currentStatus) ;
/**
* 活動執(zhí)行
*
* @param activityId 活動ID
* @param currentStatus 當(dāng)前狀態(tài)
* @return 執(zhí)行結(jié)果
*/
public abstract Result doing ( String activityId, Enum< Status> currentStatus) ;
}
在整個(gè)接口中提供了各項(xiàng)狀態(tài)流轉(zhuǎn)服務(wù)的接口,例如;活動提審、審核通過、審核拒絕、撤審撤銷等7個(gè)方法。 在這些方法中所有的入?yún)⒍际且粯拥?#xff0c;activityId(活動ID
)、currentStatus(當(dāng)前狀態(tài)
),只有他們的具體實(shí)現(xiàn)是不同的。
2.2 部分狀態(tài)流轉(zhuǎn)實(shí)現(xiàn)
編輯
public class EditingState extends State {
public Result arraignment ( String activityId, Enum< Status> currentStatus) {
ActivityService. execStatus ( activityId, currentStatus, Status. Check) ;
return new Result ( "0000" , "活動提審成功" ) ;
}
public Result checkPass ( String activityId, Enum< Status> currentStatus) {
return new Result ( "0001" , "編輯中不可審核通過" ) ;
}
public Result checkRefuse ( String activityId, Enum< Status> currentStatus) {
return new Result ( "0001" , "編輯中不可審核拒絕" ) ;
}
@Override
public Result checkRevoke ( String activityId, Enum< Status> currentStatus) {
return new Result ( "0001" , "編輯中不可撤銷審核" ) ;
}
public Result close ( String activityId, Enum< Status> currentStatus) {
ActivityService. execStatus ( activityId, currentStatus, Status. Close) ;
return new Result ( "0000" , "活動關(guān)閉成功" ) ;
}
public Result open ( String activityId, Enum< Status> currentStatus) {
return new Result ( "0001" , "非關(guān)閉活動不可開啟" ) ;
}
public Result doing ( String activityId, Enum< Status> currentStatus) {
return new Result ( "0001" , "編輯中活動不可執(zhí)行活動中變更" ) ;
}
}
提審
public class CheckState extends State {
public Result arraignment ( String activityId, Enum< Status> currentStatus) {
return new Result ( "0001" , "待審核狀態(tài)不可重復(fù)提審" ) ;
}
public Result checkPass ( String activityId, Enum< Status> currentStatus) {
ActivityService. execStatus ( activityId, currentStatus, Status. Pass) ;
return new Result ( "0000" , "活動審核通過完成" ) ;
}
public Result checkRefuse ( String activityId, Enum< Status> currentStatus) {
ActivityService. execStatus ( activityId, currentStatus, Status. Refuse) ;
return new Result ( "0000" , "活動審核拒絕完成" ) ;
}
@Override
public Result checkRevoke ( String activityId, Enum< Status> currentStatus) {
ActivityService. execStatus ( activityId, currentStatus, Status. Editing) ;
return new Result ( "0000" , "活動審核撤銷回到編輯中" ) ;
}
public Result close ( String activityId, Enum< Status> currentStatus) {
ActivityService. execStatus ( activityId, currentStatus, Status. Close) ;
return new Result ( "0000" , "活動審核關(guān)閉完成" ) ;
}
public Result open ( String activityId, Enum< Status> currentStatus) {
return new Result ( "0001" , "非關(guān)閉活動不可開啟" ) ;
}
public Result doing ( String activityId, Enum< Status> currentStatus) {
return new Result ( "0001" , "待審核活動不可執(zhí)行活動中變更" ) ;
}
}
這里提供了兩個(gè)具體實(shí)現(xiàn)類的內(nèi)容,編輯狀態(tài)和提審狀態(tài)。 例如在這兩個(gè)實(shí)現(xiàn)類中,checkRefuse
這個(gè)方法對于不同的類中有不同的實(shí)現(xiàn),也就是不同狀態(tài)下能做的下一步流轉(zhuǎn)操作已經(jīng)可以在每一個(gè)方法中具體控制了。 其他5個(gè)類的操作是類似的具體就不在這里演示了,大部分都是重復(fù)代碼??梢酝ㄟ^源碼進(jìn)行學(xué)習(xí)理解。
2.3 狀態(tài)處理服務(wù)
public class StateHandler {
private Map< Enum< Status> , State> stateMap = new ConcurrentHashMap < Enum< Status> , State> ( ) ;
public StateHandler ( ) {
stateMap. put ( Status. Check, new CheckState ( ) ) ; // 待審核
stateMap. put ( Status. Close, new CloseState ( ) ) ; // 已關(guān)閉
stateMap. put ( Status. Doing, new DoingState ( ) ) ; // 活動中
stateMap. put ( Status. Editing, new EditingState ( ) ) ; // 編輯中
stateMap. put ( Status. Open, new OpenState ( ) ) ; // 已開啟
stateMap. put ( Status. Pass, new PassState ( ) ) ; // 審核通過
stateMap. put ( Status. Refuse, new RefuseState ( ) ) ; // 審核拒絕
}
public Result arraignment ( String activityId, Enum< Status> currentStatus) {
return stateMap. get ( currentStatus) . arraignment ( activityId, currentStatus) ;
}
public Result checkPass ( String activityId, Enum< Status> currentStatus) {
return stateMap. get ( currentStatus) . checkPass ( activityId, currentStatus) ;
}
public Result checkRefuse ( String activityId, Enum< Status> currentStatus) {
return stateMap. get ( currentStatus) . checkRefuse ( activityId, currentStatus) ;
}
public Result checkRevoke ( String activityId, Enum< Status> currentStatus) {
return stateMap. get ( currentStatus) . checkRevoke ( activityId, currentStatus) ;
}
public Result close ( String activityId, Enum< Status> currentStatus) {
return stateMap. get ( currentStatus) . close ( activityId, currentStatus) ;
}
public Result open ( String activityId, Enum< Status> currentStatus) {
return stateMap. get ( currentStatus) . open ( activityId, currentStatus) ;
}
public Result doing ( String activityId, Enum< Status> currentStatus) {
return stateMap. get ( currentStatus) . doing ( activityId, currentStatus) ;
}
}
這是對狀態(tài)服務(wù)的統(tǒng)一控制中心,可以看到在構(gòu)造函數(shù)中提供了所有狀態(tài)和實(shí)現(xiàn)的具體關(guān)聯(lián),放到Map數(shù)據(jù)結(jié)構(gòu)中。 同時(shí)提供了不同名稱的接口操作類,讓外部調(diào)用方可以更加容易的使用此項(xiàng)功能接口,而不需要像在itstack-demo-design-19-01
例子中還得傳兩個(gè)狀態(tài)來判斷。
3. 測試驗(yàn)證
3.1 編寫測試類(Editing2Arraignment)
@Test
public void test_Editing2Arraignment ( ) {
String activityId = "100001" ;
ActivityService. init ( activityId, Status. Editing) ;
StateHandler stateHandler = new StateHandler ( ) ;
Result result = stateHandler. arraignment ( activityId, Status. Editing) ;
logger. info ( "測試結(jié)果(編輯中To提審活動):{}" , JSON. toJSONString ( result) ) ;
logger. info ( "活動信息:{} 狀態(tài):{}" , JSON. toJSONString ( ActivityService. queryActivityInfo ( activityId) ) , JSON. toJSONString ( ActivityService. queryActivityInfo ( activityId) . getStatus ( ) ) ) ;
}
測試結(jié)果
23 : 59 : 20.883 [ main] INFO org. itstack. demo. design. test. ApiTest - 測試結(jié)果( 編輯中To提審活動) :{ "code" : "0000" , "info" : "活動提審成功" }
23 : 59 : 20.907 [ main] INFO org. itstack. demo. design. test. ApiTest - 活動信息:{ "activityId" : "100001" , "activityName" : "早起學(xué)習(xí)打卡領(lǐng)獎(jiǎng)活動" , "beginTime" : 1593694760892 , "endTime" : 1593694760892 , "status" : "Check" } 狀態(tài):"Check"
Process finished with exit code 0
測試編輯中To提審活動,的狀態(tài)流轉(zhuǎn)。
3.2 編寫測試類(Editing2Open)
@Test
public void test_Editing2Open ( ) {
String activityId = "100001" ;
ActivityService. init ( activityId, Status. Editing) ;
StateHandler stateHandler = new StateHandler ( ) ;
Result result = stateHandler. open ( activityId, Status. Editing) ;
logger. info ( "測試結(jié)果(編輯中To開啟活動):{}" , JSON. toJSONString ( result) ) ;
logger. info ( "活動信息:{} 狀態(tài):{}" , JSON. toJSONString ( ActivityService. queryActivityInfo ( activityId) ) , JSON. toJSONString ( ActivityService. queryActivityInfo ( activityId) . getStatus ( ) ) ) ;
}
測試結(jié)果
23 : 59 : 36.904 [ main] INFO org. itstack. demo. design. test. ApiTest - 測試結(jié)果( 編輯中To開啟活動) :{ "code" : "0001" , "info" : "非關(guān)閉活動不可開啟" }
23 : 59 : 36.914 [ main] INFO org. itstack. demo. design. test. ApiTest - 活動信息:{ "activityId" : "100001" , "activityName" : "早起學(xué)習(xí)打卡領(lǐng)獎(jiǎng)活動" , "beginTime" : 1593694776907 , "endTime" : 1593694776907 , "status" : "Editing" } 狀態(tài):"Editing"
Process finished with exit code 0
測試編輯中To開啟活動,的狀態(tài)流轉(zhuǎn)。
3.3 編寫測試類(Refuse2Doing)
@Test
public void test_Refuse2Doing ( ) {
String activityId = "100001" ;
ActivityService. init ( activityId, Status. Refuse) ;
StateHandler stateHandler = new StateHandler ( ) ;
Result result = stateHandler. doing ( activityId, Status. Refuse) ;
logger. info ( "測試結(jié)果(拒絕To活動中):{}" , JSON. toJSONString ( result) ) ;
logger. info ( "活動信息:{} 狀態(tài):{}" , JSON. toJSONString ( ActivityService. queryActivityInfo ( activityId) ) , JSON. toJSONString ( ActivityService. queryActivityInfo ( activityId) . getStatus ( ) ) ) ;
}
測試結(jié)果
23 : 59 : 46.339 [ main] INFO org. itstack. demo. design. test. ApiTest - 測試結(jié)果( 拒絕To活動中) :{ "code" : "0001" , "info" : "審核拒絕不可執(zhí)行活動為進(jìn)行中" }
23 : 59 : 46.352 [ main] INFO org. itstack. demo. design. test. ApiTest - 活動信息:{ "activityId" : "100001" , "activityName" : "早起學(xué)習(xí)打卡領(lǐng)獎(jiǎng)活動" , "beginTime" : 1593694786342 , "endTime" : 1593694786342 , "status" : "Refuse" } 狀態(tài):"Refuse"
Process finished with exit code 0
測試拒絕To活動中,的狀態(tài)流轉(zhuǎn)。
3.4 編寫測試類(Refuse2Revoke)
@Test
public void test_Refuse2Revoke ( ) {
String activityId = "100001" ;
ActivityService. init ( activityId, Status. Refuse) ;
StateHandler stateHandler = new StateHandler ( ) ;
Result result = stateHandler. checkRevoke ( activityId, Status. Refuse) ;
logger. info ( "測試結(jié)果(拒絕To撤審):{}" , JSON. toJSONString ( result) ) ;
logger. info ( "活動信息:{} 狀態(tài):{}" , JSON. toJSONString ( ActivityService. queryActivityInfo ( activityId) ) , JSON. toJSONString ( ActivityService. queryActivityInfo ( activityId) . getStatus ( ) ) ) ;
}
測試結(jié)果
23 : 59 : 50.197 [ main] INFO org. itstack. demo. design. test. ApiTest - 測試結(jié)果( 拒絕To撤審) :{ "code" : "0000" , "info" : "撤銷審核完成" }
23 : 59 : 50.208 [ main] INFO org. itstack. demo. design. test. ApiTest - 活動信息:{ "activityId" : "100001" , "activityName" : "早起學(xué)習(xí)打卡領(lǐng)獎(jiǎng)活動" , "beginTime" : 1593694810201 , "endTime" : 1593694810201 , "status" : "Editing" } 狀態(tài):"Editing"
Process finished with exit code 0
七、總結(jié)
從以上的兩種方式對一個(gè)需求的實(shí)現(xiàn)中可以看到,在第二種使用設(shè)計(jì)模式處理后已經(jīng)沒有了ifelse
,代碼的結(jié)構(gòu)也更加清晰易于擴(kuò)展。這就是設(shè)計(jì)模式的好處,可以非常強(qiáng)大的改變原有代碼的結(jié)構(gòu),讓以后的擴(kuò)展和維護(hù)都變得容易些。 在實(shí)現(xiàn)結(jié)構(gòu)的編碼方式上可以看到這不再是面向過程的編程,而是面向?qū)ο蟮慕Y(jié)構(gòu)。并且這樣的設(shè)計(jì)模式滿足了單一職責(zé)
和開閉原則
,當(dāng)你只有滿足這樣的結(jié)構(gòu)下才會發(fā)現(xiàn)代碼的擴(kuò)展是容易的,也就是增加和修改功能不會影響整體的變化。 但如果狀態(tài)和各項(xiàng)流轉(zhuǎn)較多像本文的案例中,就會產(chǎn)生較多的實(shí)現(xiàn)類。因此可能也會讓代碼的實(shí)現(xiàn)上帶來了時(shí)間成本,因?yàn)槿绻龅竭@樣的場景可以按需評估投入回報(bào)率。主要點(diǎn)在于看是否經(jīng)常修改、是否可以做成組件化、抽離業(yè)務(wù)與非業(yè)務(wù)功能。
八、推薦閱讀
1. 重學(xué) Java 設(shè)計(jì)模式:實(shí)戰(zhàn)工廠方法模式「多種類型商品不同接口,統(tǒng)一發(fā)獎(jiǎng)服務(wù)搭建場景」
2. 重學(xué) Java 設(shè)計(jì)模式:實(shí)戰(zhàn)原型模式「上機(jī)考試多套試,每人題目和答案亂序排列場景」
3. 重學(xué) Java 設(shè)計(jì)模式:實(shí)戰(zhàn)橋接模式「多支付渠道(微信、支付寶)與多支付模式(刷臉、指紋)場景」
4. 重學(xué) Java 設(shè)計(jì)模式:實(shí)戰(zhàn)組合模式「營銷差異化人群發(fā)券,決策樹引擎搭建場景」
5. 重學(xué) Java 設(shè)計(jì)模式:實(shí)戰(zhàn)外觀模式「基于SpringBoot開發(fā)門面模式中間件,統(tǒng)一控制接口白名單場景」
6. 重學(xué) Java 設(shè)計(jì)模式:實(shí)戰(zhàn)享元模式「基于Redis秒殺,提供活動與庫存信息查詢場景」
7. 重學(xué) Java 設(shè)計(jì)模式:實(shí)戰(zhàn)備忘錄模式「模擬互聯(lián)網(wǎng)系統(tǒng)上線過程中,配置文件回滾場景」