思路
最初我拿到一個問題的時候,首先想的就是他怎么實(shí)現(xiàn)喻奥,具體的實(shí)現(xiàn)
捏悬,而面向接口編程需要先考慮好流程,明白變動點(diǎn)可能在哪里过牙,系統(tǒng)的邊界在哪里,邊界的劃分決定了模塊和服務(wù)的拆分刀疙,變動點(diǎn)決定了需要抽象和隔離的地方摧莽。想清楚再下手。
提煉一下就是:
案例
代碼重構(gòu)方案
//原來的代碼
for (ModuleEnum module : modules) {
switch (module) {
case ALBUM:
Album albumData = getAlbumData(key, pageSize, pageNumber);
if (albumData.getContent().size() == 0) {
searchData.setAlbum(null);
} else {
searchData.setAlbum(albumData);
}
break;
case ACTIVITY:
Activity activityData = getActivityData(key, pageSize, pageNumber);
if (activityData.getContent().size() == 0) {
searchData.setActivity(null);
} else {
searchData.setActivity(activityData);
}
break;
case COURSE:
Course courseData = getCourseData(key, pageSize, pageNumber);
if (courseData.getContent().size() == 0) {
searchData.setCourse(null);
} else {
searchData.setCourse(courseData);
}
break;
case BLOG:
Blog blogData = getBlogData(key, pageSize, pageNumber);
if (blogData.getBlogContents().size() == 0) {
searchData.setBlog(null);
} else {
searchData.setBlog(blogData);
}
break;
default:
}
}
這里我們看到了非常多的代碼冗余,邏輯完全一樣,但是寫了四遍.再看下面:
//這里4個方法對應(yīng)上面的調(diào)用(邏輯完全一樣,僅僅是返回值和其中一些調(diào)用的方法不一樣,我就只列出方法名了)
private Album getAlbumData(String key, int pageSize, int pageNumber) throws IOException;
private Activity getActivityData(String key, int pageSize, int pageNumber) throws IOException;
private Course getCourseData(String key, int pageSize, int pageNumber) throws Exception;
private Blog getBlogData(String key, int pageSize, int pageNumber) throws IOException;
重構(gòu)思路
對這里的重構(gòu)我考慮用如下的方法:
1.使用工廠模式
調(diào)用不同的實(shí)現(xiàn),替換switch.
2.在接口和實(shí)現(xiàn)之間增加抽象方法
,提取公共邏輯
上碼
1 枚舉實(shí)現(xiàn)工廠方法
//枚舉類
public enum ModuleEnum {
//在線課程
COURSE("0", "course", "courseindex"){
@Override
public ISearchService build() {
return new CourseSearch();
}
},
//培訓(xùn)活動
ACTIVITY("1", "activity", "activityindex") {
@Override
public ISearchService build() {
return new AlbumSearch();
}
},
//知識庫
ALBUM("2", "album", "albumindex") {
@Override
public ISearchService build() {
return new AlbumSearch();
}
},
//Q記
BLOG("3", "blog", "blogindex") {
@Override
public ISearchService build() {
return new BlogSearch();
}
};
public abstract ISearchService build();
}
這里使用枚舉
來實(shí)現(xiàn)工廠方法,因?yàn)榭紤]使用反射效率較低.
可以看到,我定義了一個抽象的build方法,返回實(shí)現(xiàn)類的接口,而在每個枚舉類中又實(shí)現(xiàn)了build方法 返回不同的實(shí)現(xiàn)類.
//調(diào)用者
ISearchService isearchservice = ModuleEnum.build();
if (isearchservice == null) {
//防御性判斷
throw new RuntimeException("不支持的模塊類型" + ModuleEnum.getName());
}
return isearchservice;
這里通過調(diào)用build方法來實(shí)現(xiàn)工廠方法的調(diào)用
2 增加抽象方法
public abstract class AbstractSearchService implements ISearchService {
private static final Logger log = LoggerFactory.getLogger(AbstractSearchService.class);
@Override
public SearchData getSearchData(String key, int pageNumber, int pageSize, SearchData searchData) throws Exception {
AbstractlModulBase modulData = getModulData(key, pageSize, pageNumber);
if (modulData.getContent().size() == 0) {
searchData.setAlbum(null);
} else {
//根據(jù)具體的子類調(diào)用具體的實(shí)現(xiàn)
searchData = setData(searchData, modulData);
}
return searchData;
}
private AbstractlModulBase getModulData(String key, int pageSize, int pageNumber) {
Page page = new Page(pageSize, pageNumber);
//從ES中查詢基礎(chǔ)數(shù)據(jù)
List<ParentBean> activityBeanList = getModulBeanList(key, page);
//獲取模塊Id
List<Integer> masterIdList = getParentIdList(activityBeanList);
log.info("搜索到的模塊id = {}", masterIdList);
//從bubbo中查詢實(shí)時數(shù)據(jù) 之后對ES中的數(shù)據(jù)和Dubbo中的實(shí)時數(shù)據(jù)進(jìn)行整合 并判斷是否有數(shù)據(jù)未同步
return getModulBeenAndMix(masterIdList, activityBeanList, page);
}
/**
* 將基類轉(zhuǎn)換成不同的子類 并賦值
* @param searchData 統(tǒng)一存儲查詢出的所有內(nèi)容
* @param modulData 基類
* @return SearchData
*/
protected abstract SearchData setData(SearchData searchData, AbstractlModulBase modulData);
/**
* ES查詢
* @param key ES查詢關(guān)鍵詞
* @param page 分頁信息
* @return ES查詢結(jié)果Been
*/
protected abstract List<ParentBean> getModulBeanList(String key, Page page);
/**
* 從bubbo中查詢實(shí)時數(shù)據(jù) 之后對ES中的數(shù)據(jù)和Dubbo中的實(shí)時數(shù)據(jù)進(jìn)行整合 并判斷是否有數(shù)據(jù)未同步
* @param masterIdList IdList
* @param beanList ES查詢集合
* @param page 分頁信息
* @return 查詢總結(jié)果
*/
protected abstract AbstractlModulBase getModulBeenAndMix(List<Integer> masterIdList, List<ParentBean> beanList, Page page);
/**
* 提取IdList
* @param parentBeans been
* @return IdList
*/
private List<Integer> getParentIdList(List<? extends ParentBean> parentBeans) {
Preconditions.checkNotNull(parentBeans);
return Lists.transform(parentBeans, new Function<ParentBean, Integer>() {
@Override
public Integer apply(ParentBean input) {
return input.getId();
}
});
}
}
這里針對具體的邏輯進(jìn)行拆分 并將具體的實(shí)現(xiàn)交由子類完成
一些設(shè)計(jì)思路
功能設(shè)計(jì)
- 模塊功能的改變、增加卖哎、移除删性;系統(tǒng)流程的變化
- 適應(yīng)業(yè)務(wù)發(fā)展的一種常態(tài)(頻繁的,不可抗拒的)
- 設(shè)計(jì)系統(tǒng)時考慮:模塊解耦(避免牽一發(fā)而動全身)蹬挺; 面向接口編程(盡量減少對調(diào)用方影響)
- 修改系統(tǒng)時考慮:擴(kuò)展 > 修改 (遵循“開閉原則”) → 功能兼容
程序設(shè)計(jì)
1. 問題拆分,劃分模塊 明確輸入輸出溯泣,暫時忽略代碼細(xì)節(jié)(切忌代碼堆疊) → 定義接口(面向接口編程)
參數(shù)校驗(yàn) → 命令識別與解析 → 文件讀取 → 命令執(zhí)行 → 結(jié)果輸出
變化:
- 參數(shù)校驗(yàn)?zāi)K(功能改變):不再校驗(yàn)源文件目錄參數(shù)
- 文件讀取模塊(新流程不再使用):不再需要使用文件讀取模塊
- 結(jié)果輸出模塊(功能改變):由輸出到結(jié)果文件 → 通過日志到控制臺 [LocalFileOutputService → LogOutputService]
模塊功能變化后的幾種改動方式(不分優(yōu)劣,取決于具體場景):
- 直接修改原模塊邏輯:核心邏輯未變客给,局部微調(diào),bugfix
- 繼承原有模塊:優(yōu)勢:復(fù)用父類邏輯靶剑;局限性:單繼承
- 新增模塊實(shí)現(xiàn):核心邏輯變化池充,入?yún)⑽醋兓ㄈ缃Y(jié)果輸出:輸出文件 → 輸出日志)
- 擴(kuò)展接口方法:核心邏輯未變,入?yún)⒆兓?(不同方法適用于不同的場景,方法重載纵菌,類比構(gòu)造方法)
優(yōu)勢:功能聚合,適用不同場景笛辟;公共邏輯可以復(fù)用
2. 數(shù)據(jù)模型的建立 → 抽象(關(guān)注當(dāng)前問題域的屬性)序苏、繼承
- 命令實(shí)體:關(guān)注:(類型,選項(xiàng)忱详,參數(shù))
- 文件數(shù)據(jù):關(guān)注:(內(nèi)容,文件名) 忽略(創(chuàng)建時間监透,訪問權(quán)限等)
- 不再代表原始文件數(shù)據(jù):而是代表中間處理結(jié)果
3. 過程抽象 → 分離變與不變(模板方法模式)
- 流程發(fā)生變化
- 對外接口不變
4. 各個模塊代碼的實(shí)現(xiàn) → 代碼規(guī)范
- 變量命名:見名知意航唆、駝峰命名、常量大寫下劃線分隔等
- 異常處理:
- 拋出or處理
- 自定義異常:類型糯钙、原因、執(zhí)行現(xiàn)場信息再榄、異常棧(便于定位問題代碼)
- 日志記錄:遠(yuǎn)離System.out.println(性能:加鎖+阻塞當(dāng)前線程)
5. 性能優(yōu)化 → 可選
多文件流式處理(緩解內(nèi)存壓力)享潜,線程池執(zhí)行(提高吞吐量)等等。
最后
這里我的實(shí)現(xiàn)還不是很完善,但思路基本就是這樣,重構(gòu)完之后代碼可擴(kuò)展性更好,代碼冗余減少.