一崇败、概述
隨著業(yè)務的發(fā)展盅称,工程的逐漸增大與開發(fā)人員增多,很多工程都走向了模塊化后室、組件化缩膝、插件化道路,來方便大家的合作開發(fā)與降低業(yè)務之間的耦合度岸霹。
現(xiàn)在就和大家談談模塊化的交互問題疾层,首先看下模塊化的幾個優(yōu)勢。
模塊化的優(yōu)勢:
1贡避,結構清晰:業(yè)務獨立痛黎,代碼實現(xiàn)分離,不會攪在一起刮吧。
2,便于協(xié)作:每個開發(fā)同學只要自己負責的模塊井厌,沒有太多的耦合致讥。
3拄踪,便于維護:各模塊管理自己的代碼、布局惶桐、資源贿衍,主工程可以方便添加與移除救恨。
特點:高內聚、低耦合擎淤。
常見的模塊化實現(xiàn)方式有兩種:
1嘴拢、業(yè)務 Module 都放到同一個工程里寂纪。
2、每個業(yè)務 Module 都是一個獨立的工程孝冒。
如圖:
!
模塊的劃分:
模塊可分為多種類型庄涡,一般分為:三方的基礎 SDK (網(wǎng)絡請求搬设,地圖導航,推送等)迹鹅;自己平臺的通用功能(網(wǎng)絡請求的能力封裝斜棚、圖片加載能力封裝该窗、權限設置、UI組件等)义钉;
業(yè)務模塊的拆分(登錄捶闸、交易、會員删壮、硬件等)。
模塊間通信:
雖然功能已經按模塊拆分税灌,但是模塊間通信也是多種形勢菱涤,如果處理不好模塊之間耦合嚴重維護成本增大洛勉。
常見模塊問通信有:直接依賴坯认、事件或廣播通信、路由通信陋气、面向接口通信引润,下面就對比下幾種通信優(yōu)勢淳附。
二、實現(xiàn)方案
直接依賴:
這種方式實現(xiàn)簡單别凹,但是耦合太嚴重炉菲,不方便維護與開發(fā)坤溃,當工程逐漸增大模塊逐漸增多,依賴關系會非常復雜祠饺,不推薦這種方式汁政。
事件或廣播通信:
EventBus: 我們非常熟悉的事件總線型的通信框架缀旁,非常靈活诵棵,采用注解方式實現(xiàn)祝旷,但是難以追溯事件怀跛。
廣播: 安卓的四大組件之一柄冲,在一個模塊中發(fā)送廣播設置數(shù)據(jù),在另一個模塊中注冊廣播接收數(shù)據(jù)漓拾,使用廣播進行數(shù)據(jù)傳遞方式廣播相對于其他的方式而言消耗資源較多骇两。
總結: BroadcastReceiver姜盈、EventBus,非常靈活示血,模塊之間沒有任何的耦合难审,但是代碼的可讀性差亿絮,難以追溯事件,不是很推薦葱绒。
路由通信:
模塊與模塊之間不存在依賴關系斗锭,而是各自運作岖是,簡單的來說就是映射關系的路由通信实苞,也是目前比較主流的一種方案黔牵,比較常用的開源框架是阿里的ARouter爷肝。
ARouter典型應用
從外部URL映射到內部頁面灯抛,以及參數(shù)傳遞與解析
跨模塊頁面跳轉,模塊間解耦
攔截跳轉過程夹抗,處理登陸纵竖、埋點等邏輯
跨模塊API調用靡砌,通過控制反轉來做組件解耦
面向接口通信:
以上幾種方式只是簡單的介紹,下面就具體說下通過接口解耦通信的方式摆舟,首先先看幾個問題邓了。
什么是面向接口編程?
接口大家都很熟悉骗炉,這里所說的面向接口編程,并不只是所謂的 java 中的 interface厕鹃,而是指超類型剂碴,可以是接口也可以是抽象類轻专。
面向接口比面向對象編程是更先進一步編程思想,而是附屬于面向對象編程的體系催训,屬于其中一部分,它是面向對象編程體系中的思想精髓之一亚兄。面向接口編程它的核心思想是將抽象與實現(xiàn)分離采驻,從組件的級別來設計代碼挑宠,達到高內聚低耦合的目的颓影。面向接口編程方法是,先定義底層接口模塊碎浇,也就是 通信的協(xié)議與功能約定 奴璃,是提供方實現(xiàn)對應的功能與能力城豁。
在架構中層次分明,不需要關注具體實現(xiàn)雳旅,開發(fā)中可以通過接口快速制定協(xié)議攒盈,與提供能力api哎榴,對于上層通過接口顯露能力,對于下層只需要依賴接口層相當于依賴api迎变。
面向接口編程的好處衣形?
靈活性高沒有依賴具體的實體热凹,實現(xiàn)層可以任意的更改與切換。在模塊化中可以相互依賴service(接口層)或依賴多個纪铺。
在模塊化中的使用
下面對于接口(interface)或api層統(tǒng)稱為service,其含義為服務提供者突诬。
對于旺隙,每一個 module 都一個獨立的工程結構骏令,每個 module 都有自己的 Service ,來統(tǒng)一暴露當前 module 所擁有能力與向外提供的服務。
對于 module 是在同一個工程里的項目結構周拐,service 可以放到統(tǒng)一的一個 Module 下妥粟,我們統(tǒng)稱為 Mediator勾给,這樣做的目的是為了減少 Module 創(chuàng)建與維護锅知。假設你的工程有20個業(yè)務 Module 如果都同時增加一個 service 層就會造成 Module 數(shù)量翻一倍喉镰。由于這里存入的都一些接口類,也是每個業(yè)務 Module 向外提供的服務其體量不會太大侣姆,這里并只是一個建議并沒有標準的做法捺宗。
當然也有更復雜的設計,一個 Module 又分不同的 service 實現(xiàn)如圖长已,這里不在展開細說。
實際工程中使用與設計
在實際項目中有很多項目都同時開發(fā)兩版本Pad與Phone康聂,有的是兩獨立的工程恬汁,有的是在同一個工程內用 flavor 切換不同的工程辜伟,下面我就以通過 flavor 切換的工程結構舉例。
先看下工程的包的結構圖:
可以看到 module 結構是分為三個部分约巷,common, pad, phone, 如果每個service 都獨立將增加3倍的 Module 數(shù)量独郎。
使用一個 Mediator Module 統(tǒng)一管理這這些 service 就很好控制了 module 數(shù)量囚聚。
service 創(chuàng)建
在 module_mediator 業(yè)務 module 下 common,pad茁计、pone 下分別創(chuàng)建ICommonService, IService(pad), IService(phone)。
ICommonService:公共服務践剂。
IService(pad):pad服務并繼承CommonService馋评。
IService(phone):phone服務并繼承CommonService竣贪。
注:這里為什么不用,PadService與PhoneService匕争,是因為pad與phone版本同時只會存在一個爷耀,使用方只需要關心你提供的Service不用在區(qū)分版本,而且這里是一個繼承關系也可以獲取到共用的部分跑杭。
service 實現(xiàn)
依賴 Mediator :
在業(yè)務 common\pad\phone module 下分別實現(xiàn)德谅,ICommonService, Service(pad), IService(phone) ,在 common module 創(chuàng)建 CommonServiceImpl 實現(xiàn) ICommonService宅荤,在 pad冯键、phone module 分別創(chuàng)建 ServiceImpl 對應實現(xiàn) IService 并繼承與 CommonServiceImpl庸汗。
service 注冊
注冊的方式有一般是通過代碼用去注冊改化,或通過注解進行注冊〕赂兀可以在 Application 注冊也可以在業(yè)務 Module 下自己注冊句旱,如果使用注解則可以自動注冊晰奖,具體要看項目怎樣實現(xiàn)。
例:
解釋下 MediatorServiceFacator啃匿,它只是一個服務工廠也是一個接口類溯乒,作用是負責管理各業(yè)務方的 Service 主要功能是注冊與獲取 Service臊岸。上面的代碼就是往里注冊了一個會員的 Service。
可以看出這個函數(shù)只有兩個參數(shù)灯帮,一個是接口class一個是實現(xiàn)類class,第一個參數(shù)cls:它會作為 key 來使用钟哥,第二個參數(shù)implClass:它會作為 value 來使用腻贰。
service 使用
通過 MediatorServiceFacator 懶加載獲取service對象,如果業(yè)務方沒有注冊則獲取一個空的對象冀瓦。
注冊有 service 沒有使用時是不會創(chuàng)建的写烤,如果使用過則會緩存下來洲炊,下次調用則直接返回暂衡。(第一次是通過反射創(chuàng)建)
例:
1、在 mediator 模塊下會員 CommonService 中 定義了一個模糊查詢會員的方法撑毛。
2代态、在會員模塊下 common 中實現(xiàn)了該功能疹吃。
3萨驶、在會員模塊下 pad 中繼承了這個實現(xiàn)腔呜。
4核畴、在其他模塊 pad 下使用這個功能谤草。
可以看到獲取 Service 只要傳對應接口就即可,對于使用方是不用關心實現(xiàn)方冀宴,在開發(fā)過程中只要先定義好接口温学,合作的同學就可以進入正常開發(fā)了。
細心的同學可以看出逃延,返回的數(shù)據(jù)類型也是一個接口類揽祥,為什么不直接返回一個普通 java 類呢?主要原因是通過接口方法達到雙方 api 約定紧帕,例如 getName() :String 方法是通過方法名返回值達到約定效果是嗜,這樣不依賴具體實現(xiàn)。
從上面的例子可以看出主要分為三個部分:
1站绪、定義接口恢准。
2馁筐、提供方實現(xiàn)接口坠非。
3、使用方都通過服務工廠獲取服務使用盟迟。
對于使用者來是很簡單的攒菠,不需要關心實現(xiàn)辖众,通過接口可以直接獲取到實現(xiàn)卓起,并且獲取到結果可以直接使用,不需要做序列化處理赵辕。
有了路由通信我們?yōu)槭裁催€使用面向接口編程既绩?
路由模式雖然很好的解決了耦合的問題,但他的方法調用都是靜態(tài)的还惠,對于傳參與返回值只能是基本類型饲握,如果是對象需要做序列化與反序列化處理,對性能有一定影響蚕键。
類似在調后臺接口一樣救欧,同時降低了代碼的可讀性, 對于 app 而言所有 Module 都是在同一個應用下锣光,沒有必要做這些序列化操作笆怠。
對于復雜業(yè)務不好處理蹬刷,例如一個業(yè)務需要多次通信,路由模式則不好處理搂漠,而通過接口通信則可以容易解決而克。
例如:
一個讀卡的操作,業(yè)務方需要對它有開啟充活、關閉映穗、暫停等多個狀態(tài)的操作宿接。通過接口則可以直接返回一個讀卡的 service 控制器睦霎, 這樣可以直接進行相應的控制操作蛤高。
從上面代碼中可以看出,上層回調結果的同時并回調了一個控制接口,這樣就提供使用方一個反向操作的能力喜庞。
三、總結
通過路由通信窄潭,可以很好的解決模塊間耦合,但拿不到對象無法持續(xù)交互幽污,并且需要序列化,而通過接口通信准潭,則很好地解決了這一點刑然,并且代碼可讀性比較高怔软。而接口通信會有一定的耦合性,也就是依賴了提供方的 service 層相當 api家坎,但對于收益來說還是值得的方式。
在架構模式中订框,沒有最好的模式只有比較合適自己項目的模式,希望大家都可以活學活用矛物,謝謝。