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

思路

最初我拿到一個(gè)問題的時(shí)候,首先想的就是他怎么實(shí)現(xiàn),具體的實(shí)現(xiàn),而面向接口編程需要先考慮好流程,明白變動(dòng)點(diǎn)可能在哪里,系統(tǒng)的邊界在哪里,邊界的劃分決定了模塊和服務(wù)的拆分,變動(dòng)點(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個(gè)方法對(duì)應(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)思路

對(duì)這里的重構(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)活動(dòng)
    ACTIVITY("1", "activity", "activityindex") {
        @Override
        public ISearchService build() {
            return  new AlbumSearch();
        }
    },
    //知識(shí)庫
    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)榭紤]使用反射效率較低.

可以看到,我定義了一個(gè)抽象的build方法,返回實(shí)現(xiàn)類的接口,而在每個(gè)枚舉類中又實(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í)數(shù)據(jù) 之后對(duì)ES中的數(shù)據(jù)和Dubbo中的實(shí)時(shí)數(shù)據(jù)進(jìn)行整合 并判斷是否有數(shù)據(jù)未同步
        return getModulBeenAndMix(masterIdList, activityBeanList, page);
    }

    /**
     * 將基類轉(zhuǎn)換成不同的子類 并賦值
     * @param searchData 統(tǒng)一存儲(chǔ)查詢出的所有內(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í)數(shù)據(jù) 之后對(duì)ES中的數(shù)據(jù)和Dubbo中的實(shí)時(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();
            }

        });
    }
}

這里針對(duì)具體的邏輯進(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)時(shí)考慮:模塊解耦(避免牽一發(fā)而動(dòng)全身); 面向接口編程(盡量減少對(duì)調(diào)用方影響)
  4. 修改系統(tǒng)時(shí)考慮:擴(kuò)展 > 修改 (遵循“開閉原則”) → 功能兼容

程序設(shè)計(jì)

1. 問題拆分,劃分模塊 明確輸入輸出,暫時(shí)忽略代碼細(xì)節(jié)(切忌代碼堆疊) → 定義接口(面向接口編程)

參數(shù)校驗(yàn) → 命令識(shí)別與解析 → 文件讀取 → 命令執(zhí)行 → 結(jié)果輸出


變化:

  • 參數(shù)校驗(yàn)?zāi)K(功能改變):不再校驗(yàn)源文件目錄參數(shù)
  • 文件讀取模塊(新流程不再使用):不再需要使用文件讀取模塊
  • 結(jié)果輸出模塊(功能改變):由輸出到結(jié)果文件 → 通過日志到控制臺(tái) [LocalFileOutputService → LogOutputService]

模塊功能變化后的幾種改動(dòng)方式(不分優(yōu)劣,取決于具體場(chǎng)景):

  • 直接修改原模塊邏輯:核心邏輯未變,局部微調(diào),bugfix
  • 繼承原有模塊:優(yōu)勢(shì):復(fù)用父類邏輯;局限性:?jiǎn)卫^承
  • 新增模塊實(shí)現(xiàn):核心邏輯變化,入?yún)⑽醋兓ㄈ缃Y(jié)果輸出:輸出文件 → 輸出日志)
  • 擴(kuò)展接口方法:核心邏輯未變,入?yún)⒆兓?(不同方法適用于不同的場(chǎng)景,方法重載,類比構(gòu)造方法)

優(yōu)勢(shì):功能聚合,適用不同場(chǎng)景;公共邏輯可以復(fù)用

2. 數(shù)據(jù)模型的建立 → 抽象(關(guān)注當(dāng)前問題域的屬性)、繼承

  • 命令實(shí)體:關(guān)注:(類型,選項(xiàng),參數(shù))
  • 文件數(shù)據(jù):關(guān)注:(內(nèi)容,文件名) 忽略(創(chuàng)建時(shí)間,訪問權(quán)限等)
  • 不再代表原始文件數(shù)據(jù):而是代表中間處理結(jié)果

3. 過程抽象 → 分離變與不變(模板方法模式)

  • 流程發(fā)生變化
  • 對(duì)外接口不變

4. 各個(gè)模塊代碼的實(shí)現(xiàn) → 代碼規(guī)范

  • 變量命名:見名知意、駝峰命名、常量大寫下劃線分隔等
  • 異常處理:
    1. 拋出or處理
    2. 自定義異常:類型、原因、執(zhí)行現(xiàn)場(chǎng)信息、異常棧(便于定位問題代碼)
  • 日志記錄:遠(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)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

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

友情鏈接更多精彩內(nèi)容