面向接口編程-模塊化的設(shè)計(jì)思想

思路

最初我拿到一個問題的時候,首先想的就是他怎么實(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ì)

  1. 模塊功能的改變、增加卖哎、移除删性;系統(tǒng)流程的變化
  2. 適應(yīng)業(yè)務(wù)發(fā)展的一種常態(tài)(頻繁的,不可抗拒的)
  3. 設(shè)計(jì)系統(tǒng)時考慮:模塊解耦(避免牽一發(fā)而動全身)蹬挺; 面向接口編程(盡量減少對調(diào)用方影響)
  4. 修改系統(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ī)范

  • 變量命名:見名知意航唆、駝峰命名、常量大寫下劃線分隔等
  • 異常處理:
    1. 拋出or處理
    2. 自定義異常:類型糯钙、原因、執(zhí)行現(xiàn)場信息再榄、異常棧(便于定位問題代碼)
  • 日志記錄:遠(yuǎn)離System.out.println(性能:加鎖+阻塞當(dāng)前線程)

5. 性能優(yōu)化 → 可選

多文件流式處理(緩解內(nèi)存壓力)享潜,線程池執(zhí)行(提高吞吐量)等等。

最后

這里我的實(shí)現(xiàn)還不是很完善,但思路基本就是這樣,重構(gòu)完之后代碼可擴(kuò)展性更好,代碼冗余減少.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末窝革,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子虐译,更是在濱河造成了極大的恐慌,老刑警劉巖侮攀,帶你破解...
    沈念sama閱讀 217,277評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件厢拭,死亡現(xiàn)場離奇詭異,居然都是意外死亡供鸠,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評論 3 393
  • 文/潘曉璐 我一進(jìn)店門薄坏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來寨闹,“玉大人,你說我怎么就攤上這事繁堡。” “怎么了闻牡?”我有些...
    開封第一講書人閱讀 163,624評論 0 353
  • 文/不壞的土叔 我叫張陵绳矩,是天一觀的道長。 經(jīng)常有香客問我,道長烧栋,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,356評論 1 293
  • 正文 為了忘掉前任珍特,我火速辦了婚禮魔吐,結(jié)果婚禮上莱找,老公的妹妹穿的比我還像新娘。我一直安慰自己奥溺,他們只是感情好骨宠,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著桦卒,像睡著了一般匿又。 火紅的嫁衣襯著肌膚如雪方灾。 梳的紋絲不亂的頭發(fā)上碌更,一...
    開封第一講書人閱讀 51,292評論 1 301
  • 那天针贬,我揣著相機(jī)與錄音,去河邊找鬼桦他。 笑死,一個胖子當(dāng)著我的面吹牛快压,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播坪郭,決...
    沈念sama閱讀 40,135評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼脉幢,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了嫌松?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,992評論 0 275
  • 序言:老撾萬榮一對情侶失蹤液走,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后缘眶,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,429評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡该抒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評論 3 334
  • 正文 我和宋清朗相戀三年柔逼,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片愉适。...
    茶點(diǎn)故事閱讀 39,785評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡癣漆,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出癌蓖,到底是詐尸還是另有隱情,我是刑警寧澤租副,帶...
    沈念sama閱讀 35,492評論 5 345
  • 正文 年R本政府宣布较性,位于F島的核電站,受9級特大地震影響赞咙,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜院仿,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評論 3 328
  • 文/蒙蒙 一速和、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧颠放,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽伸头。三九已至匾效,卻和暖如春恤磷,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背魔策。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評論 1 269
  • 我被黑心中介騙來泰國打工河胎, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人游岳。 一個月前我還...
    沈念sama閱讀 47,891評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像喷户,于是被迫代替她去往敵國和親访锻。 傳聞我的和親對象是個殘疾皇子褪尝,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評論 2 354

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,099評論 25 707
  • 貼吧比朋友圈更容易展示自己: 1、前期不要一上來就發(fā)廣告貼 就算級別高容达,百度不刪,吧主看你不順眼也會刪花盐,說實(shí)話,我...
    倪青語閱讀 376評論 0 0
  • 從前很鄙視全職媽媽算芯,覺得自己一定不會整天圍著老公、孩子轉(zhuǎn)职祷,不管任何時候都要做一個自力更生的現(xiàn)代女性。 ...
    博8429閱讀 287評論 0 0
  • 也還是這種夜晚有梆,也還是一樣的心情意系。靜靜的走在月色中,被薄霧籠罩昔字,蒙上面紗。街燈早早地就亮了作郭,早早地撕破黃昏,燃燒黑...
    擁抱謊言擁抱你i閱讀 180評論 0 0